[LibreOJ 2135][ZJOI2015]幻想乡战略游戏
竟然1A了蛤蛤蛤蛤蛤
这题用人话说就是给你一个不会动的树,让你求带权重心。
先解决一个问题:如果要求动态修改点权,并且要求多次询问求某点\(x\)的\(\sum_{i = 1}^n d_i\cdot dist(i, x)\),怎么做?
很显然对于一个点\(x\),对它造成贡献的点,可能是在以\(x\)为根的点分子树里的,也可能在\(x\)之外。但是一个不在该点分子树内的点要对\(x\)产生贡献,必须要经过\(x\)在分治树上的祖先。
所以我们可以考虑对每个重心,记录下它这个点分子树中所有点对该重心的贡献、对点分树上的父亲的贡献以及该点分子树的点权和。每次求答案时\(x\)所在的分治子树的贡献很好考虑,那考虑一下其分治树上祖先对\(x\)的贡献就行了。
那带权重心怎么求呢?如果我们把树的根变成带权重心,那么我们会发现越远离根则答案越劣。所以我们如果只往使得答案更小的地方走,那么最后一定会走到带权重心上。
我们考虑把这个过程放点分树上。从整棵树的重心开始往下走,每次如果发现有一条出边指向的点答案更好,那就往这个子结构(不是指向的点而是那个联通块的重心,否则时间复杂度不对)里走。最后走不动了,走到的点就是带权重心了。
求LCA我用的是\(O(logn)\)的树剖而不是\(O(1)\)搞法,但是跑的贼快(可能和树剖常数小有关?)。
吐槽一点,原题提到了每个点的度数不超过20,但是网上几乎所有OJ上该题的题面都没提到这一点……
代码:
#include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #include <algorithm> #include <utility> #include <set> #include <vector> #include <queue> #include <stack> typedef long long ll; const int maxn = 100005; const int maxe = maxn << 1; int first[maxn]; int next[maxe], to[maxe]; ll dist[maxe]; void add_edge(int u, int v, ll d) { static int cnt = 0; cnt ++; next[cnt] = first[u]; first[u] = cnt; to[cnt] = v; dist[cnt] = d; } int n; int size[maxn], hson[maxn], fa[maxn], dep[maxn]; ll dis[maxn]; void dfs_1(int x, int father = 0, int depth = 1, ll d = 0) { size[x] = 1; fa[x] = father; dep[x] = depth; dis[x] = d; int max_siz = 0; for(int i = first[x]; i; i = next[i]) { int v = to[i]; if(v != father) { dfs_1(v, x, depth + 1, d + dist[i]); size[x] += size[v]; if(size[v] > max_siz) { max_siz = size[v]; hson[x] = v; } } } } int dfn[maxn], top[maxn], tid[maxn]; void dfs_2(int x, int father = 0, int a = 1) { static int dfn_clk = 0; dfn[x] = ++ dfn_clk; tid[dfn[x]] = x; top[x] = a; if(!hson[x]) { return; } else { dfs_2(hson[x], x, a); } for(int i = first[x]; i; i = next[i]) { int v = to[i]; if(v != father && v != hson[x]) { dfs_2(v, x, v); } } } int lca(int x, int y) { while(top[x] != top[y]) { if(dep[top[x]] < dep[top[y]]) std::swap(x, y); x = fa[top[x]]; } if(dep[x] > dep[y]) { return y; } else { return x; } } ll calc_dist(int x, int y) { int l = lca(x, y); return dis[x] + dis[y] - 2LL * dis[l]; } bool vis[maxn]; int siz[maxn]; void gen_siz(int x, int f = 0) { siz[x] = 1; for(int i = first[x]; i; i = next[i]) { int v = to[i]; if(!vis[v] && v != f) { gen_siz(v, x); siz[x] += siz[v]; } } } int nrt, rt; void gen_rt(int x, int f = 0) { bool flag = (siz[x] * 2 >= siz[nrt]); for(int i = first[x]; i; i = next[i]) { int v = to[i]; if(!vis[v] && v != f) { flag = (flag && (siz[v] * 2 <= siz[nrt])); gen_rt(v, x); } } if(flag) rt = x; } ll w[maxn]; int crt, cfa[maxn]; ll pans[maxn], give[maxn], sumv[maxn]; int point2[maxe]; /* void search_p(int x, std::stack<int> *V, int f = 0) { V -> push(x); for(int i = first[x]; i; i = next[i]) { int v = to[i]; if(!vis[v] && v != f) { search_p(v, V, x); } } } */ int divide(int x) { nrt = rt = x; gen_siz(x); gen_rt(x); x = rt; vis[x] = true; for(int i = first[x]; i; i = next[i]) { int v = to[i]; if(!vis[v]) { int ch = divide(v); point2[i] = ch; cfa[ch] = x; } } return x; } void update(int x, ll delta) { int p = x; while(p) { pans[p] += delta * calc_dist(p, x); if(p != crt) give[p] += delta * calc_dist(x, cfa[p]); sumv[p] += delta; p = cfa[p]; } } ll get_ans(int x) { ll ans = pans[x]; int p = x; while(p != crt) { int f = cfa[p]; ans += pans[f] - give[p]; ans += calc_dist(x, f) * (sumv[f] - sumv[p]); p = cfa[p]; } return ans; } ll get_best() { int p = crt; std::stack<int> S; ll now_ans; while(true) { S.push(p); vis[p] = true; bool fix = true; now_ans = get_ans(p); for(int i = first[p]; i; i = next[i]) { int v = to[i]; if(vis[v]) continue; if(get_ans(v) < now_ans) { fix = false; p = point2[i]; break; } } if(fix) break; } while(!S.empty()) { int u = S.top(); S.pop(); vis[u] = false; } return now_ans; } int main() { int q; scanf("%d%d", &n, &q); for(int i = 0; i < n - 1; i ++) { int u, v; ll d; scanf("%d%d%lld", &u, &v, &d); add_edge(u, v, d); add_edge(v, u, d); } dfs_1(1); dfs_2(1); crt = divide(1); memset(vis, 0, sizeof(vis)); while(q --) { int u, e; scanf("%d%d", &u, &e); update(u, e); printf("%lld\n", get_best()); } return 0; }
[BZOJ 1095][ZJOI2007]Hide 捉迷藏
蛤蛤蛤我这题终于调出来了~(然后1A了)
点分树处女作。其实点分树的思想并不难理解,就是把点分的过程抽象化到一棵树上。
其实唯一比较烦人的就是点分树上的儿子给父亲的贡献要开一个堆处理……非常烦人。
代码(其中有海量本来拿来调试的代码,慎读):
/************************************************************** Problem: 1095 User: danihao123 Language: C++ Result: Accepted Time:15268 ms Memory:128944 kb ****************************************************************/ #include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #include <algorithm> #include <utility> #include <set> #include <vector> #include <queue> const int maxn = 100005; const int maxe = maxn << 1; int first[maxn]; int next[maxe], to[maxe]; void add_edge(int u, int v) { static int cnt = 0; cnt ++; next[cnt] = first[u]; first[u] = cnt; to[cnt] = v; } int size[maxn], hson[maxn], fa[maxn], dep[maxn]; void dfs_1(int x, int father = 0, int depth = 1) { size[x] = 1; fa[x] = father; dep[x] = depth; int max_siz = 0; for(int i = first[x]; i; i = next[i]) { int v = to[i]; if(v != father) { dfs_1(v, x, depth + 1); size[x] += size[v]; if(size[v] > max_siz) { max_siz = size[v]; hson[x] = v; } } } } int dfn[maxn], top[maxn], tid[maxn]; void dfs_2(int x, int father = 0, int a = 1) { static int dfn_clk = 0; dfn[x] = ++ dfn_clk; tid[dfn[x]] = x; top[x] = a; if(!hson[x]) { return; } else { dfs_2(hson[x], x, a); } for(int i = first[x]; i; i = next[i]) { int v = to[i]; if(v != father && v != hson[x]) { dfs_2(v, x, v); } } } int lca(int x, int y) { while(top[x] != top[y]) { if(dep[top[x]] < dep[top[y]]) std::swap(x, y); x = fa[top[x]]; } if(dep[x] > dep[y]) { return y; } else { return x; } } bool vis[maxn]; int siz[maxn]; void gen_siz(int x, int fa = 0) { siz[x] = 1; for(int i = first[x]; i; i = next[i]) { int v = to[i]; if(!vis[v] && v != fa) { gen_siz(v, x); siz[x] += siz[v]; } } } int nrt, rt; void gen_rt(int x, int fa = 0) { bool ok = (2 * siz[x] >= siz[nrt]); for(int i = first[x]; i; i = next[i]) { int v = to[i]; if(!vis[v] && v != fa) { ok = ok && (2 * siz[v] <= siz[nrt]); gen_rt(v, x); } } if(ok) rt = x; } int crt; int cfa[maxn]; typedef std::priority_queue<int> heap; struct dheap { heap Q, D; void push(int x) { Q.push(x); } void pop() { while(!D.empty() && Q.top() == D.top()) { #ifdef DEBUG printf("deleting %d\n", D.top()); #endif Q.pop(); D.pop(); } Q.pop(); } int top() { while(!D.empty() && Q.top() == D.top()) { #ifdef DEBUG printf("deleting %d\n", D.top()); #endif Q.pop(); D.pop(); } return Q.top(); } void del(int x) { D.push(x); } size_t size() { return Q.size() - D.size(); } int sec() { int fir = top(); pop(); int ret = top(); push(fir); return ret; } }; dheap all; dheap Q[maxn], sg[maxn]; int divide(int x) { nrt = rt = x; gen_siz(x); gen_rt(x); x = rt; vis[x] = true; for(int i = first[x]; i; i = next[i]) { int v = to[i]; if(!vis[v]) { cfa[divide(v)] = x; } } return x; } int n; int get_ans(dheap &q) { return q.top() + q.sec(); } bool sta[maxn]; int dist(int x, int y) { int ret = dep[x] + dep[y] - 2 * dep[lca(x, y)]; #ifdef DEBUG printf("dist(%d, %d) : %d\n", x, y, ret); #endif return ret; } void light_on(int p) { sta[p] = true; int x = p; int og = -1, ng = 0; while(x) { int old_ans = -1, old_sg = -1; if(Q[x].size() > 1) { old_ans = get_ans(Q[x]); } if(sg[x].size() > 0) { old_sg = sg[x].top(); } if(og != -1) Q[x].del(og); if(ng != -1) Q[x].push(ng); if(Q[x].size() > 1) all.push(get_ans(Q[x])); if(old_ans != -1) all.del(old_ans); if(cfa[x] != 0) { og = old_sg; sg[x].push(dist(p, cfa[x])); ng = sg[x].top(); } x = cfa[x]; } } void light_off(int p) { sta[p] = false; int x = p; int og = 0, ng = -1; while(x) { int old_ans = -1, old_sg = -1; if(Q[x].size() > 1) old_ans = get_ans(Q[x]); if(sg[x].size() > 0) { old_sg = sg[x].top(); } if(og != -1) Q[x].del(og); if(ng != -1) Q[x].push(ng); #ifdef DEBUG printf("%d deleting %d\n", x, og); #endif if(Q[x].size() > 1) all.push(get_ans(Q[x])); if(old_ans != -1) all.del(old_ans); #ifdef DEBUG int tmp = -1; if(Q[x].size() > 1) tmp = get_ans(Q[x]); printf("ans of %d : %d -> %d\n", x, old_ans, tmp); #endif if(cfa[x] != 0) { og = old_sg; sg[x].del(dist(p, cfa[x])); if(sg[x].size() > 0) { ng = sg[x].top(); } else { ng = -1; } } x = cfa[x]; } } void print_dheap(dheap &q) { std::vector<int> V; while(q.size() > 0) { int t = q.top(); q.pop(); printf("%d ", t); V.push_back(t); } puts(""); for(int i = 0; i < V.size(); i ++) { q.push(V[i]); } } int main() { scanf("%d", &n); for(int i = 1; i <= n - 1; i ++) { int u, v; scanf("%d%d", &u, &v); add_edge(u, v); add_edge(v, u); } dfs_1(1); dfs_2(1); #ifdef DEBUG for(int i = 1; i <= n; i ++) { printf("%d top fa hson : %d %d %d\n", i, top[i], fa[i], hson[i]); } #endif crt = divide(1); #ifdef DEBUG printf("crt id %d\n", crt); for(int i = 1; i <= n; i ++) { printf("cfa[%d] : %d\n", i, cfa[i]); } #endif int num = 0; for(int i = 1; i <= n; i ++) { light_on(i); num ++; #ifdef DEBUG printf("heap while inserting %d : ", i); print_dheap(all); #endif } int q; scanf("%d", &q); while(q --) { char op[3]; scanf("%s", op); if(op[0] == 'G') { if(num == 0) { puts("-1"); } else if(num == 1) { puts("0"); } else { printf("%d\n", all.top()); } } else { int i; scanf("%d", &i); if(sta[i]) { light_off(i); num --; } else { light_on(i); num ++; } } #ifdef DEBUG print_dheap(all); #endif } return 0; }
[BZOJ 2599][IOI2011]Race
由于一些琐事,很长时间没有更新博客了……尽管做了不少题,但还是给我些时间整理思绪吧……
大方向肯定是点分治没错啦……
点分治的题处理经过根的路径通常有两种套路……一种是类似BZOJ 1468那样,收集所有儿子的信息,然后一次性合并,并排除不合法的情况。另一种类似于这道题,顺次处理儿子的信息,处理一个合并一个。
具体的思路是,用[tex]p[x][/tex]表示目前已知的所有从根开始长度为[tex]x[/tex]的路径(当然根本身也算上啦,所以说一开始就把[tex]p[root][/tex]设为0),然后根搜集信息时每次对一个儿子进行DFS,假设某结点[tex]x[/tex]到根的距离为[tex]d[x][/tex],那么显然[tex]x[/tex]对答案的贡献为[tex]p[k-d[x]][/tex],之后把这个子树合并到[tex]p[/tex]中。
注意清理[tex]p[/tex]的时候不能直接暴力memset……这样会被卡常致死。更好的方法是对于每次把子树合并到[tex]p[/tex]中时,把[tex]x[/tex]记录下来,然后清理[tex]p[/tex]时常数就小很多了。
代码:
/************************************************************** Problem: 2599 User: danihao123 Language: C++ Result: Accepted Time:14640 ms Memory:23532 kb ****************************************************************/ #include <cstdio> #include <cstring> #include <cctype> #include <algorithm> using namespace std; const int maxn = 200005; const int maxm = maxn * 2; const int maxk = 1000005; struct Edge { Edge *next; int to, dist; }; Edge pool[maxm]; int graph_cnt; Edge *first[maxn]; inline void clear_graph() { graph_cnt = 0; memset(first, 0, sizeof first); } inline void add_edge(int u, int v, int d) { Edge *e = &pool[graph_cnt++]; e->next = first[u]; first[u] = e; e->to = v; e->dist = d; } int k; bool vis[maxn]; int siz[maxn]; void gen_siz(int x, int fa) { siz[x] = 1; for(Edge *e = first[x]; e; e = e->next) { int v = e->to; if(!vis[v] && v != fa) { gen_siz(v, x); siz[x] += siz[v]; } } } int now_root, best_root; void gen_best_root(int x, int fa) { bool OK = siz[x]*2 >= siz[now_root]; for(Edge *e = first[x]; e; e = e->next) { int v = e->to; if(!vis[v] && v != fa) { gen_best_root(v, x); OK = OK && (siz[v]*2 <= siz[now_root]); } } if(OK) { best_root = x; } } int buf[maxk]; int ans; void deal_ans(int x, int fa, int dep, int d) { if(d > k) { return; } ans = min(ans, dep + buf[k-d]); for(Edge *e = first[x]; e; e = e->next) { int v = e->to; if(!vis[v] && v != fa) { deal_ans(v, x, dep + 1, d + e->dist); } } } void add_to_buf(int x, int fa, int dep, int d) { if(d > k) { return; } buf[d] = min(buf[d], dep); for(Edge *e = first[x]; e; e = e->next) { int v = e->to; if(!vis[v] && v != fa) { add_to_buf(v, x, dep + 1, d + e->dist); } } } void clear_buf(int x, int fa, int dep, int d) { if(d > k) { return; } buf[d] = 0x3f3f3f3f; for(Edge *e = first[x]; e; e = e->next) { int v = e->to; if(!vis[v] && v != fa) { clear_buf(v, x, dep + 1, d + e->dist); } } } void divide(int x) { gen_siz(x, 0); now_root = best_root = x; gen_best_root(x, 0); x = best_root; vis[x] = true; buf[0] = 0; for(Edge *e = first[x]; e; e = e->next) { int v = e->to; if(!vis[v]) { deal_ans(v, x, 1, e->dist); add_to_buf(v, x, 1, e->dist); } } for(Edge *e = first[x]; e; e = e->next) { int v = e->to; if(!vis[v]) { clear_buf(v, x, 1, e->dist); } } for(Edge *e = first[x]; e; e = e->next) { int v = e->to; if(!vis[v]) { divide(v); } } } inline int readint() { int x = 0; char c = getchar(); while(!isdigit(c)) { c = getchar(); } while(isdigit(c)) { x = x * 10 + (c - '0'); c = getchar(); } return x; } int main() { int n; n = readint(); k = readint(); clear_graph(); for(int i = 1; i <= (n-1); i++) { int u, v, d; u = readint(); v = readint(); d = readint(); u++; v++; add_edge(u, v, d); add_edge(v, u, d); } ans = 0x3f3f3f3f; memset(buf, 0x3f, sizeof buf); divide(1); if(ans == 0x3f3f3f3f) { ans = -1; } printf("%d\n", ans); return 0; }
[BZOJ 1468]Tree
点分治第一题……
这个也就是最经典的那个点分治问题了吧……参照qzc大爷的论文吧。
代码:
/************************************************************** Problem: 1468 User: danihao123 Language: C++ Result: Accepted Time:816 ms Memory:2736 kb ****************************************************************/ #include <cstdio> #include <cstring> #include <vector> #include <algorithm> using namespace std; const int maxn = 40005; const int maxm = maxn * 2; struct Edge { Edge *next; int to, dist; }; Edge pool[maxm]; int graph_cnt; Edge *first[maxn]; inline void clear_graph() { graph_cnt = 0; memset(first, 0, sizeof first); } inline void add_edge(int u, int v, int d) { Edge *e = &pool[graph_cnt++]; e->next = first[u]; first[u] = e; e->to = v; e->dist = d; } int k; int calc(vector<int>& V) { int size = V.size(); int rc = size - 1; int ans = 0; if(size <= 1){ return 0; } for(int i = 0; i < size; i++) { while(i < rc && V[i] + V[rc] > k) { rc--; } if(i == rc) { break; } ans += rc - i; } return ans; } bool vis[maxn]; int siz[maxn]; void gen_siz(int x, int fa) { siz[x] = 1; for(Edge *e = first[x]; e; e = e->next) { int v = e->to; if(!vis[v] && v != fa) { gen_siz(v, x); siz[x] += siz[v]; } } } int now_root, best_root; void gen_best_root(int x, int fa) { bool OK = siz[x]*2 >= siz[now_root]; for(Edge *e = first[x]; e; e = e->next) { int v = e->to; if(!vis[v] && v != fa) { gen_best_root(v, x); OK = OK && (siz[v]*2 <= siz[now_root]); } } if(OK) { best_root = x; } } void add_to_V(int x, int fa, int d, vector<int>& V) { V.push_back(d); for(Edge *e = first[x]; e; e = e->next) { int v = e->to; if(!vis[v] && v != fa) { add_to_V(v, x, d + e->dist, V); } } } int divide(int x) { gen_siz(x, 0); now_root = x; gen_best_root(x, 0); x = best_root; vis[x] = true; vector<int> V, CV; int ans = 0; for(Edge *e = first[x]; e; e = e->next) { int v = e->to; if(vis[v]) { continue; } CV.clear(); add_to_V(v, x, e->dist, CV); sort(CV.begin(), CV.end()); ans -= calc(CV); for(int i = 0; i < CV.size(); i++) { V.push_back(CV[i]); } } V.push_back(0); sort(V.begin(), V.end()); ans += calc(V); for(Edge *e = first[x]; e; e = e->next) { int v = e->to; if(vis[v]) { continue; } ans += divide(v); } return ans; } int main() { int n; scanf("%d", &n); clear_graph(); for(int i = 1; i <= (n - 1); i++) { int u, v, d; scanf("%d%d%d", &u, &v, &d); add_edge(u, v, d); add_edge(v, u, d); } scanf("%d", &k); printf("%d\n", divide(1)); return 0; }