题解 P4427 【[BJOI2018]求和】

shadowice1984

2018-04-25 19:20:30

Solution

大概是树上差分的题? 先介绍下什么是树上差分……(说实在的应该叫树上前缀和比较好) 大概是我们要算路径上的一些数(通常是求和),然后计算路径信息的时候我们处理一些信息,然后通过算$Dep_{u}+Dep_{v}-2Dep_{lca(u,v)}$来计算路径上的信息 题目看起来非常有树链剖分的感觉……但是问题是,这道题没有修改这就导致题目难度大幅度下降了,所以我们先把每个点深度的k次方打一个表,之后我们因为要做减法,所以我们令$val_{i,k}$表示i到1号点路径上点深度的k次方之和…… 然后问题来了,我们维护的是点权和,所以呢我们发现直接减的话会导致lca这个点没算,所以略微改一下公式,使lca这个点也被算一次 然后我们询问$u,v,k$的时候输出$val_{u,k}+val_{v,k}-val_{lca(u,v)}-val_{fa_{lca(u,v)}}$即可 问题就是如何找lca了……~~(欢迎使用TarjanO(n)求lca)~~ ~~欢迎使用st表O(1)查询~~ 然而最粗暴,可靠性最强,常数小而不会被轻易卡的算法还是倍增,所以这道题上一个倍增板子即可通过此题了~(不会倍增法求lca的话出门左转你站膜板区,包教包会~) 上代码~ ```C #include<cstdio> #include<algorithm> using namespace std;typedef long long ll;const ll mod=998244353;const int N=3*1e5+10;const int M=60; int n;int m;int v[2*N];int x[2*N];int al[N];int ct;ll dep[N];int fa[N][22];int book[N];ll val[N][M]; inline void add(int u,int V){v[++ct]=V;x[ct]=al[u];al[u]=ct;}ll mi[M]; inline void dfs(int u)//处理val的信息以及倍增的信息 { book[u]=true;for(int i=0;fa[u][i];i++){fa[u][i+1]=fa[fa[u][i]][i];} for(int i=al[u];i;i=x[i]) { if(book[v[i]]){continue;} fa[v[i]][0]=u;dep[v[i]]=dep[u]+1; for(int j=1;j<=50;j++){mi[j]=mi[j-1]*dep[v[i]]%mod;} for(int j=1;j<=50;j++){val[v[i]][j]=(mi[j]+val[u][j])%mod;} dfs(v[i]); } } inline int lca(int u,int v)//倍增求lca { if(dep[u]<dep[v]){swap(u,v);}int d=dep[u]-dep[v]; for(int i=0;d;d>>=1,i++){if(d&1){u=fa[u][i];}}if(u==v){return u;} for(int i=20;i>=0;i--){if(fa[u][i]!=fa[v][i]){u=fa[u][i];v=fa[v][i];}} return fa[u][0]; } int main() { scanf("%d",&n);mi[0]=1; for(int i=1,u,v;i<n;i++){scanf("%d%d",&u,&v);add(u,v);add(v,u);}dfs(1);//dfs处理 scanf("%d",&m); for(int i=1,u,v,k;i<=m;i++) { scanf("%d%d%d",&u,&v,&k);int l=lca(u,v);//然后我们就直接减了,注意lca只算但是要算一次 printf("%lld\n",(val[u][k]+val[v][k]+2*mod-val[fa[l][0]][k]-val[l][k])%mod); }return 0;//拜拜程序~ } ```