[树上差分&树链剖分] [GXOI/GZOI2019]旧词
黑题?看起来很难的样子。(所以写篇题解纪念一下)
我们还是看原版吧。
首先看到这个奇怪的查询,我们首先想到了莫队。
(莫队:把区间询问按区间位置排好再处理,离线。)
每次y会变化,所以不能预处理出LCA。
莫队后x会递增,考虑每次在上次答案的基础上加上新增x的贡献。
但是因为y不同,所以并不能直接加入答案。
所以我们要找到一种 与y没有关系 的求解方法。
树上差分。
简单地说,就是使得节点x到根节点root的路径上的权值和等于结点x的贡献,显然这个权值是我们加上的。
怎么实现呢?
有一个数组dpk[ i ] ,表示i号节点的k次方。
现在我们要一个数组,使得从头加到i的和等于dpk[i]。
想到了什么?
没错,差分数组。
(差分数组diff[i] = a[i]-a[i-1] ,差分数组的前缀和等于原数组。)
于是,这题的基本思路就出来了。
对于每个x(指已经经过的节点),把root ~ x的路径上的节点都搞成“差分”,也就是若 u 是路径上的一个节点,则有 val[u] += dpk[u] - dpk[f[u]]。
这样,从root ~ x的和就等于dpk[x]了。(而且也可以求出路径上的任一个点的dpk,这句话很重要。)
回到y。
怎么求所有LCA的dpk和呢?
考虑只有一个节点x,也就是普通的LCA。
显然x与y的LCA,是root~x和root~y两条路径上最深的一个交点,于是我们又发现,只要把root~y上的和加起来,得到的就是两路径重合部分的最深一个点的dpk,这个点是谁?是LCA。
同理,在有多个节点的情况下也适用,因为每次加的值不会干扰。
root~x使用树剖处理。
上代码,
开5e4+5的数组RE了,于是我开了5e5+5。
因为用int型,乘法会溢出,所以写了个快速乘,longlong直接乘法。
#include<iostream> #include<cstdio> #include<algorithm> typedef long long LL; const int Mod=998244353,N=5e5+5; int mul(int a,int b){ int base=0; while(b){ if(b&1)(base+=a)%=Mod; (a+=a)%=Mod;b>>=1; }return base; } int powe(int a,LL b){ int base=1; a%=Mod; while(b){ if(b&1)(base=mul(base,a))%=Mod; (a=mul(a,a))%=Mod;b>>=1; }return base; } struct EDGE{ int nt,v; }e[N]; int tot=0,hd[N],val[N]; void add_edge(int u,int v){ e[++tot]=(EDGE){hd[u],v}; hd[u]=tot; } int n,Q,dpk[N]; LL k; int f[N],dep[N],siz[N],son[N]; int ontree_pos[N],real_pos[N],top[N],tag[N],tpos=0; namespace Tree { void dfs1(int u,int deep){ dep[u]=deep; dpk[u]=powe(deep,k); siz[u]=1; for(int i=hd[u];i;i=e[i].nt){ int v=e[i].v; dfs1(v,deep+1); siz[u]+=siz[v]; if(siz[v]>siz[son[u]]) son[u]=v; } } void dfs2(int u,int topn){ ontree_pos[u]=++tpos; real_pos[tpos]=u; top[u]=topn; if(!son[u])return; dfs2(son[u],topn); for(int i=hd[u];i;i=e[i].nt){ int v=e[i].v; if(v!=son[u])dfs2(v,v); } } /* */ void build(int rt,int l,int r){ if(l==r){ int u=ontree_pos[l]; (val[rt]=powe(dep[u],k)+Mod -powe(dep[f[u]],k))%=Mod; } int mid=(l+r)>> 1; build(rt<<1,l,mid); build(rt<<1|1,mid+1,r); } /* */ #define p(u) ontree_pos[u] #define add(rt,l,r,t) \ (val[rt]+=mul((dpk[r]+Mod-dpk[f[l]])%Mod,t))%=Mod; void pushdown(int rt,int l,int r){ if(!tag[rt]||l==r)return; int LS=rt<<1,RS=rt<<1|1; int mid=(l+r)>>1; add(LS,real_pos[l],real_pos[mid],tag[rt]); add(RS,real_pos[mid+1],real_pos[r],tag[rt]); tag[LS]+=tag[rt]; tag[RS]+=tag[rt]; tag[rt]=0; } void update(int rt,int l,int r,int x,int y){ if(l>=x&&r<=y){ add(rt,real_pos[l],real_pos[r],1); ++tag[rt]; return ; } pushdown(rt,l,r); int mid=(l+r)>>1; if(x<=mid)update(rt<<1,l,mid,x,y); if(y>mid)update(rt<<1|1,mid+1,r,x,y); val[rt]=(val[rt<<1]+val[rt<<1|1])%Mod; return; } int query(int rt,int l,int r,int x,int y){ if(l>=x&&r<=y)return val[rt]; pushdown(rt,l,r); int mid=(l+r)>>1,res=0; if(x<=mid)(res+=query(rt<<1,l,mid,x,y))%=Mod; if(y>mid)(res+=query(rt<<1|1,mid+1,r,x,y))%=Mod; return res; } void Add_link(int x){ while(p(top[x])!=1){ update(1,1,n,p(top[x]),p(x)); x=f[top[x]]; }update(1,1,n,1,p(x)); } int Query_link(int x){ int res=0; while(p(top[x])!=1){ (res+=query(1,1,n,p(top[x]),p(x)))%=Mod; x=f[top[x]]; }(res+=query(1,1,n,1,p(x)))%=Mod; return res; } #undef p #undef add } using namespace std; using namespace Tree; struct Que{ int x,y,ans,pos; }q[N]; bool cmp1(Que a,Que b){ return a.x<b.x; } bool cmp2(Que a,Que b){ return a.pos<b.pos; } int main(){ scanf("%d%d%lld",&n,&Q,&k); for(int i=2;i<=n;++i){ scanf("%d",&f[i]); add_edge(f[i],i); } for(int i=1;i<=Q;++i){ scanf("%d%d",&q[i].x,&q[i].y); q[i].pos=i; } sort(q+1,q+1+Q,cmp1); dfs1(1,1);dfs2(1,1); int nowx=1; for(int i=1;i<=Q;++i){ while(nowx<=q[i].x) Add_link(nowx),++nowx; q[i].ans=Query_link(q[i].y); } sort(q+1,q+1+Q,cmp2); for(int i=1;i<=Q;++i) printf("%d\n",q[i].ans); return 0; }