CF916E Jamie and Tree
先贴个代码,详细说明以后有时间再来填,代码中有很详细的注释,看不懂可以先私信我。
// Problem: CF916E Jamie and Tree // Memory Limit: 250 MB // Time Limit: 2500 ms #include <bits/stdc++.h> using namespace std; const int MAXN = 1e5 + 50; #define int long long int tot = 0,start[MAXN]; int n,Q,A[MAXN],dfn_N[MAXN],now = 0,root = 1; struct Node { int dfn,siz,fa,tp,son,dep; } N[MAXN]; struct Edge { int next,to; void add(int u,int v) {to = v , next = start[u] , start[u] = tot;} } edge[MAXN * 2]; struct SegmentTree { int l,r,sum,laz; } T[MAXN * 4]; inline int read() { int x = 0 , flag = 1; char ch = getchar(); for( ; ch > '9' || ch < '0' ; ch = getchar()) if(ch == '-') flag = -1; for( ; ch >= '0' && ch <= '9' ; ch = getchar()) x = (x << 3) + (x << 1) + ch - '0'; return x * flag; } int DFS1(int x,int from) { N[x].siz = 1 , N[x].son = 0 , N[x].fa = from; N[x].dep = N[from].dep + 1; for(int i = start[x] ; i ; i = edge[i].next) { int to = edge[i].to; if(to == from) continue; int v = DFS1(to , x); if(v > N[N[x].son].siz) N[x].son = to; N[x].siz += v; } return N[x].siz; } void DFS2(int x,int top) { N[x].tp = top; N[x].dfn = ++now; dfn_N[now] = x; if(N[x].son) DFS2(N[x].son , top); for(int i = start[x] ; i ; i = edge[i].next) { int to = edge[i].to; if(to == N[x].fa || to == N[x].son) continue; DFS2(to , to); } return ; } void build_Tree(int x,int l,int r) { T[x].l = l , T[x].r = r; T[x].laz = 0; T[x].sum = 0; if(l == r) {T[x].sum = A[dfn_N[l]] ; return ;} int mN = (l + r) >> 1; build_Tree(x << 1 , l , mN); build_Tree(x << 1 | 1 , mN + 1 , r); T[x].sum = T[x << 1].sum + T[x << 1 | 1].sum; return ; } int GetLCA(int x,int y) { // 推荐树链剖分求LCA,毕竟都打好了树链剖分.... while(N[x].tp != N[y].tp) { if(N[N[x].tp].dep < N[N[y].tp].dep) swap(x , y); x = N[N[x].tp].fa; } if(N[x].dep < N[y].dep) return x; return y; } int pdLCA(int x,int y) { //手动画重点,这是这个题目最难的地方!密集注释来临 if( N[x].dep > N[y].dep ) swap(x , y);//手动使得x的深度小于等于y的深度 if(GetLCA(x,y) == x) { //倘若x是y的祖先 if(N[root].dfn >= N[y].dfn && N[root].dfn <= N[y].dfn + N[y].siz - 1) return y; //倘若当前根root在原来的树中是y的儿子,自己画图就知道,那么肯定离当前根越近的就是祖先,所以返回y //如果不是上面的情况,也就是意味着当前根root可能是夹在 x,y中间 或者 x,y在原本的树中都是当前根的儿子 if(GetLCA(x,root) == x) return GetLCA(y,root); //当前根root夹在 x,y中间的话,返回y跟root的LCA,这个可以画图,不可以直接返回root return x;//如果是说当前根是 x 的祖先,直接返回 x } //下面的情况都是 x,y 不是祖先关系的情况 if(N[root].dfn >= N[x].dfn && N[root].dfn <= N[x].dfn + N[x].siz - 1) return x; //当前根在x的子树中 if(N[root].dfn >= N[y].dfn && N[root].dfn <= N[y].dfn + N[y].siz - 1) return y; //当前根在y的子树中 if( (GetLCA(x,root) == root && GetLCA(x,y) == GetLCA(y,root)) || /*上面这一行的意思是,root是x的祖先,并且x,y在原来的树中的LCA是 y 和 root 的LCA,那么应该返回根。 其实用画图说明这个if,则是说,x,y 一个处于当前根的子树中, 另外一个呢并不处于当前根的子树中,那么就意味着 x,y被 当前根“隔开了”,所以返回root 下面一行同理。 */ (GetLCA(y,root) == root && GetLCA(x,y) == GetLCA(x,root)) )return root; if(GetLCA(x,root) == GetLCA(y,root)) return GetLCA(x,y);//这个就表明,x,y均不是root子树中的节点,就没有影响 if(GetLCA(x,y) != GetLCA(x,root)) return GetLCA(x,root); /*这里就是说,x到y的路径中,与当前 root 到 x 的路径的交集只有x 到 LCA(x,root) 这一段,可以画图进行理解*/ return GetLCA(y ,root);//同理 } void ad(int x,int k) { T[x].sum += (T[x].r - T[x].l + 1) * k; T[x].laz += k; return ; } void pushdown(int x) { if(T[x].laz == 0) return ; ad(x << 1 , T[x].laz); ad(x << 1 | 1 , T[x].laz); T[x].laz = 0; return ; } void change(int x,int l,int r,int k) {//线段树区间修改,这个应该都会 if(T[x].l >= l && T[x].r <= r) { ad(x , k); return ; } pushdown(x); int mid = (T[x].l + T[x].r) >> 1; if(l <= mid) change(x << 1 , l , r , k); if(r > mid) change(x << 1 | 1 , l , r , k); T[x].sum = T[x << 1].sum + T[x << 1 | 1].sum; return ; } int GetAns(int x,int l,int r) { //线段树区间求和,这个应该都会 if(T[x].l >= l && T[x].r <= r) return T[x].sum ; int mid = (T[x].l + T[x].r) >> 1 , sum = 0; pushdown(x); if(l <= mid) sum += GetAns(x << 1 , l , r ); if(r > mid) sum += GetAns(x << 1 | 1 , l , r ); return sum; } int GetFa(int x,int y) { //寻找某个点 x 的一个儿子,使得这个儿子离x距离为1而且是y的祖先 while(N[x].tp != N[y].tp) { if(N[N[x].tp].dep < N[N[y].tp].dep) swap(x,y); if(N[N[x].tp].fa == y) return N[x].tp; x = N[N[x].tp].fa; } if(N[x].dep > N[y].dep) swap(x,y); return N[x].son; } signed main() { n = read(); Q = read(); for(int i = 1 ; i <= n ; i ++) A[i] = read(); for(int i = 2 ; i <= n ; i ++) { int u = read() , v = read(); edge[++tot].add(u,v); edge[++tot].add(v,u); } DFS1(1,0);DFS2(1,1);root = 1; build_Tree(1 , 1 , n); while(Q --) { int op = read(); if(op == 1) root = read(); //直接换根 if(op == 2) { int u = read() ,v = read() , D = read(); int x = pdLCA(u , v); if(x == root) change(1, 1 , n , D);//表示是就是给出的点是根,直接整棵树修改 else if(x == GetLCA(root,x)) { //表示是在1到当前根的路径上 change(1 , 1 , n , D); int z = GetFa(x , root); change(1, N[z].dfn , N[z].dfn + N[z].siz - 1 , -D);//要还原 } else change(1 , N[x].dfn, N[x].dfn + N[x].siz - 1 , D); //表示是在1的不同子树外,可以直接处理 } if(op == 3) { int Ans = 0; int x = read(); if(x == root) Ans = GetAns(1, 1 , n );//表示是就是给出的点是根,直接整棵树修改 else if(GetLCA(x,root) == x) { //表示是在1到当前根的路径上 Ans = GetAns(1 , 1 , n); int z = GetFa(x , root); Ans -= GetAns(1, N[z].dfn , N[z].dfn + N[z].siz - 1); } else Ans = GetAns(1 , N[x].dfn,N[x].dfn + N[x].siz - 1); //表示是在1的不同子树外,可以直接处理 cout << Ans << endl; } } return 0; }