【洛谷5305】[GXOI/GZOI2019] 旧词(LCA套路题)

点此看题面

  • 给定一棵(n)个点的以(1)为根的树。
  • (q)次询问,每次给定(x,y),求(sum_{i=1}^xdepth(LCA(i,y))^k)
  • (n,qle5 imes10^4)

经典(LCA)套路

实在是经典啊。

核心思想是发现从(x)到根和从(y)到根的路径的交集正好是从(LCA(x,y))到根的路径。

定义一种深度(d)的权值为(d^k-(d-1)^k),相当于是做了一个差分。

然后考虑(depth(LCA(x,y))^k),如果我们给(x)到根的路径上所有点都加上(1)的次数,然后询问从(y)到根路径上所有点的次数( imes)对应深度的权值,则修改路径和询问路径的交集就是(LCA(x,y))到根的路径,那么询问到的总值相当于给差分做了一遍前缀和,就是(depth(LCA(x,y))^k)

多个(x)和单个(y)之间也是一样的,因为这个贡献是可以叠加计算的。

所以我们离线询问,按照(x)排序。

枚举(x),利用树剖将它的贡献表示到树上,然后用当前前缀上的询问的对应的(y)查询。

代码:(O(nlog^2n))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 50000
#define X 998244353
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,m,k,ans[N+5],ee,lnk[N+5];struct edge {int to,nxt;}e[N+5];
struct Q {int p,x,y;I bool operator < (Con Q& o) Con {return x<o.x;}}q[N+5];
I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
	int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
	Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('
');}
}using namespace FastIO;
namespace T//树链剖分
{
	int v[N+5],d,dfn[N+5],fac[N+5],D[N+5],f[N+5],g[N+5],sz[N+5],tp[N+5];
	class SegmentTree
	{
		private:
			#define PT CI l=1,CI r=n,CI rt=1
			#define LT l,mid,rt<<1
			#define RT mid+1,r,rt<<1|1
			#define PU(x) (V[x]=(V[x<<1]+V[x<<1|1])%X)
			#define PD(x) (F[x]&&(T(x<<1,F[x]),T(x<<1|1,F[x]),F[x]=0))
			#define T(x,v) (V[x]=(1LL*P[x]*(v)+V[x])%X,F[x]+=v)
			int V[N<<2],P[N<<2],F[N<<2];
		public:
			I void Build(PT)//建树
			{
				if(l==r) return (void)(P[rt]=v[D[fac[l]]]);RI mid=l+r>>1;
				Build(LT),Build(RT),P[rt]=(P[rt<<1]+P[rt<<1|1])%X;//统计子树内权值总和
			}
			I void U(CI L,CI R,PT)//区间修改,次数加1
			{
				if(L<=l&&r<=R) return (void)T(rt,1);RI mid=l+r>>1;PD(rt);
				L<=mid&&(U(L,R,LT),0),R>mid&&(U(L,R,RT),0),PU(rt);
			}
			I int Q(CI L,CI R,PT)//区间询问次数×权值
			{
				if(L<=l&&r<=R) return V[rt];RI mid=l+r>>1;PD(rt);
				return ((L<=mid?Q(L,R,LT):0)+(R>mid?Q(L,R,RT):0))%X;
			}
	}S;
	I void dfs1(CI x)//树剖第一遍dfs
	{
		sz[x]=1;for(RI i=lnk[x];i;i=e[i].nxt) D[e[i].to]=D[x]+1,
			dfs1(e[i].to),sz[x]+=sz[e[i].to],sz[e[i].to]>sz[g[x]]&&(g[x]=e[i].to);
	}
	I void dfs2(CI x,CI t)//树剖第二遍dfs
	{
		if(fac[dfn[x]=++d]=x,tp[x]=t,!g[x]) return;dfs2(g[x],t);
		for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^g[x]&&(dfs2(e[i].to,e[i].to),0);
	}
	I void Init()//初始化
	{
		RI i;for(i=1;i<=n;++i) v[i]=QP(i,k);for(i=n;i;--i) v[i]=(v[i]-v[i-1]+X)%X;//求出每种深度对应的权值
		D[1]=1,dfs1(1),dfs2(1,1),S.Build();//树剖,建线段树
	}
	I void A(RI x) {W(x) S.U(dfn[tp[x]],dfn[x]),x=f[tp[x]];}//给到根路径次数加1
	I int Q(RI x,RI t=0) {W(x) t=(t+S.Q(dfn[tp[x]],dfn[x]))%X,x=f[tp[x]];return t;}//询问到根路径次数×权值
}
int main()
{
	RI i;for(read(n,m,k),i=2;i<=n;++i) read(T::f[i]),add(T::f[i],i);T::Init();
	RI x,y;for(i=1;i<=m;++i) read(q[i].x,q[i].y),q[i].p=i;sort(q+1,q+m+1);//离线
	RI j=1;for(i=1;i<=n;++i) {T::A(i);W(j<=m&&q[j].x==i) ans[q[j].p]=T::Q(q[j].y),++j;}//加上当前点贡献,然后处理当前前缀的询问
	for(i=1;i<=m;++i) writeln(ans[i]);return clear(),0;
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5305.html