Task
N节点的树,m个操作分2种:
① (x,y)路径上的各边的值+1
② 询问边(x,y)的值。
Solution
方法一:树链剖分
树链剖分使用于树上的区间更新或者区间询问,把链重新编号,加入线段树中。
方法二:刷漆法
问题属于区间更新,单点求值。
如果在序列上,可以用刷漆法+前缀和得到每个点的权值。
在树上可以转化成两段区间,即(x,lca),(y,lca)同样的左端点+1,右端点+1的位置-1
结论:x到父亲的边权值=x子树的权值和
但是暴力dfs x的子树会超时,如果把x的子树变成连续的一段,就可以用线段树进行区间求值。而dfs序可以将一棵子树变成连续的一段。
int L[M],R[M],fa[S][M],head[M],dep[M];
int n,m,ecnt,tot;
struct edge{
int t,nxt;
}e[M<<1];
struct Segment_Tree{
int t[M<<2];//存当前点的值
inline void update(int l,int r,int x,int a,int p){
if(l==r){
t[p]+=a;
return;
}
int mid=l+r>>1;
if(x<=mid)update(l,mid,x,a,lsn(p));
else update(mid+1,r,x,a,rsn(p));
t[p]=t[lsn(p)]+t[rsn(p)];
}
inline int query(int L,int R,int l,int r,int p){
if(l==L&&r==R)return t[p];
int mid=L+R>>1;
if(r<=mid)return query(L,mid,l,r,lsn(p));
else if(l>mid)return query(mid+1,R,l,r,rsn(p));
else return query(L,mid,l,mid,lsn(p))+query(mid+1,R,mid+1,r,rsn(p));
}
}T;
struct P100{
inline void addedge(int f,int t){
e[++ecnt]=(edge){t,head[f]};
head[f]=ecnt;
}
inline void input(){
int a,b;
rd(n);rd(m);
rep(i,1,n-1){
rd(a);rd(b);
addedge(a,b);
addedge(b,a);
}
}
inline void dfs(int f,int x,int d){
fa[0][x]=f;
dep[x]=d;
L[x]=++tot;
tral(i,x){
if(e[i].t!=f)dfs(x,e[i].t,d+1);
}
R[x]=tot;
}
inline int LCA(int x,int y){
if(dep[x]<dep[y])swap(x,y);
int d=dep[x]-dep[y];
rep(i,0,S-1){
if(d&(1<<i))x=fa[i][x];
}
if(x==y)return x;
per(i,S-1,0){
if(fa[i][x]!=fa[i][y]){
x=fa[i][x];
y=fa[i][y];
}
}
return fa[0][x];
}
inline void solve(){
int a,b;
char str[10];
rep(i,1,m){
scanf("%s",str);
rd(a);rd(b);
if(str[0]=='P'){//add
T.update(1,n,L[a],1,1);
T.update(1,n,L[b],1,1);
T.update(1,n,L[LCA(a,b)],-2,1);
}
else{
if(dep[a]<dep[b])swap(a,b);
sc(T.query(1,n,L[a],R[a],1));//子树的和
}
}
}
inline void init(){
rep(i,1,S-1)
rep(j,1,n)fa[i][j]=fa[i-1][fa[i-1][j]];
}
inline void MAIN(){
input();
dfs(0,1,0);
init();
solve();
}
}P100;