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;
} 
京公网安备 11010502036488号