【简要题意】一棵带点权的有根树,其中树根可以转换。每次询问当前根下x节点的子树权值和。
节点数、操作数<=1e5
【分析】
对于任意一个点做根,相当于是在整棵树总的权值中减去根所在的那颗"子树"。可以先固定根,求dfs序确定root所在的位置,再通过lca确定哪棵子树。
【code】
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1e5+1000;
int idx[maxn],beg[maxn],end[maxn],dfs_clock;
struct Edge{
int v,nxt;
}edge[maxn<<1];
int head[maxn],tot;
int n,m,root,val[maxn],siz[maxn],sum;
int p[maxn][20],d[maxn];
inline void read(int &x){
x=0;char tmp=getchar();int fl=1;
while(tmp<'0'||tmp>'9'){if(tmp=='-')fl=-fl;tmp=getchar();}
while(tmp>='0'&&tmp<='9') x=(x<<1)+(x<<3)+tmp-'0',tmp=getchar();
x=x*fl;
}
void dfs(int u,int dep){
idx[u]=++dfs_clock;beg[u]=idx[u]+1;
siz[u]=val[u],d[u]=dep;
for(int i=head[u];i!=-1;i=edge[i].nxt){
int v=edge[i].v;if(d[v]) continue;
p[v][0]=u;dfs(v,dep+1);siz[u]+=siz[v];
} end[u]=dfs_clock;
}
inline int lca(int x,int y){for(int i=18;i>=0;i--)if(d[y]-d[x]>(1<<i)) y=p[y][i];return y;}
int main(){
memset(head,-1,sizeof(head));
cin>>n>>m;
for(int i=1;i<n;i++){
int x,y;read(x),read(y);
edge[tot]=(Edge){y,head[x]},head[x]=tot++;
edge[tot]=(Edge){x,head[y]},head[y]=tot++;
}
for(int i=1;i<=n;i++) read(val[i]);
cin>>root;dfs(root,1);sum=siz[root];p[root][0]=root;
for(int j=1;j<=18;j++)for(int i=1;i<=n;i++)p[i][j]=p[p[i][j-1]][j-1];
for(int i=1;i<=m;i++){
int opt,x;read(opt),read(x);
if(opt==1) root=x;
else if(root==x) printf("%d\n",sum);
else if(idx[root]>=beg[x]&&idx[root]<=end[x]) printf("%d\n",sum-siz[lca(x,root)]);
else printf("%d\n",siz[x]);
}
return 0;
}
这当中的方法其实非常多,像求dfs序之后二分查找求根,树链剖分,树上分块(请选择正确的分块方法),由于随机数据甚至放过了暴力换根。欢迎光顾HG的其他blogs,知道更多奇葩解法。