CF1254D Tree Queries 题解

CF1254D Tree Queries 题解

题目大意

给定一棵 \(N\) 个节点的树,有 \(Q\) 次操作,分别是如下两种:

  • 给定一个点 \(v\) 和一个权值 \(d\),等概率地选择一个点 \(r\),对每一个点 \(u\),若 \(v\)\(u\)\(r\) 的路径上,则\(u\)的权值加上\(d\)
  • 查询 \(v\) 的权值期望,对 \(998244353\) 取模。

Solve

考虑对节点 \(u\) 执行操作 1 之后,对树上所有节点的贡献。

  • 对于 \(u\) 自己,期望权值需要加上 \(d\)
  • 对于 \(u\) 子树里(除去 \(u\) 自己)的点,若这个点在以 \(v\) 为根的子树里(\(v\in son(u)\)),那么只有选到的 \(r\) 不在 \(v\) 的子树里才对这个点有贡献,所以这个点的期望权值需要加上 \(d\cdot siz(v)\over N\)
  • 对于不在 \(u\) 子树里的点,需要选到的 \(r\)\(u\) 子树里,才能对这个点有贡献,期望权值需要加上 \(d\cdot siz(u)\over N\)

对树建立 dfs 序,那么就可以转化为区间加,用树状数组维护。

但问题是,对 \(u\) 子树里的点的贡献和那个点所在的子树有关,如果每次都遍历 \(u\) 的所有儿子,可以被菊花卡到 \(O(NQ\log_2N)\)。所以考虑一个根号分治 / 根号重建。

将操作分为若干个大小为 \(B\) 的块。记录每个块内的修改操作,把对同一个节点的修改合并。

\(B\) 次修改,遍历这个块内的所有修改并按上面的方式执行。由于每个点的所有边至多被遍历一次,复杂度 \(O(\frac Q B\cdot N\log_2N)\)

对于一次询问,它所在块前面的贡献都已经通过数据结构统计出来了,只需要遍历这个块里的所有修改就行。

比如当前询问的点是 \(u\),遍历到的修改是 \((v,d)\)。还是按上面所说的分类讨论计算贡献。那么我们需要快速求出 \(u\) 是否在 \(v\) 的子树里,若在,是在 \(v\) 的哪个儿子的子树里。可以分情况讨论。

  • \(u=v\),特判。
  • 否则若 \(u\) 的深度 \(dep(u)\geq dep(v)\),说明 \(u\) 肯定不在 \(v\) 的子树里。
  • \(dep(u)<dep(v)\),我们对 \(u\)\(dep(v)-dep(u)-1\) 级祖先,记为 \(fa\)。如果 \(fa\) 的父亲不是 \(v\),说明 \(u\) 不在 \(v\) 的子树里。否则,\(u\)\(v\) 的子树里,并且我们也已求得了它是在 \(v\) 的哪个儿子的子树里。

这样,就可以计算贡献了。时间复杂度为 \(O(QBK)\),其中 \(K\) 为求 \(k\) 级祖先的复杂度消耗。

对于求 \(k\) 级祖先,方法很多。由于我们对于不同块的操作已经带了一个 \(\log\),如果我们在这里还使用带 \(\log\) 的算法求,如树剖和倍增,那么总复杂度是在 \(B=\sqrt N\) 时取得最小值,为 \(O(Q\sqrt N \log_2N)\),较慢。实测倍增写法跑了 4000ms+。

用长链剖分求 \(k\) 级祖先,可以做到 \(O(N\log_2N)-O(1)\)。这样总复杂度在 \(B=\sqrt{N\log_2N}\) 时取得最小值 \(O(Q\sqrt{N\log_2N})\)

Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int read()
{
	short f=1;
	int x=0;
	char c=getchar();
	while(c<'0'||c>'9')	{if(c=='-')	f=-1;c=getchar();}
	while(c>='0'&&c<='9')	x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x*f;
}
const int N=1.5e5+10,B=1600,MOD=998244353;
inline int qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1)	res=res*x%MOD;
		x=x*x%MOD;y>>=1;
	}
	return res;
}
int n,q,dfn[N],tim,inv,siz[N];
ll d[N];
int dep[N],fa[N][18],logn[N]={-1};
bool in[N];
vector<int>e[N],v;
int son[N],mx[N],l[N],r[N],top[N];
void get_son(int u)//预处理重儿子,倍增预处理 2^k 级祖先
{
	mx[u]=dep[u]=-~dep[fa[u][0]];
	for(int i=1;i<=logn[dep[u]];i=-~i)
		fa[u][i]=fa[fa[u][~-i]][~-i];
	for(int i:e[u])
		if(i!=fa[u][0])
			fa[i][0]=u,get_son(i),siz[u]+=siz[i];
	if(mx[u]>mx[son[fa[u][0]]])
		son[fa[u][0]]=u,mx[fa[u][0]]=mx[u];
}
void get_chain(int u,int L)//重儿子优先遍历
{
	r[dfn[u]=tim=-~tim]=u;
	l[tim]=L;
	top[u]=fa[u][0]&&u==son[fa[u][0]]?top[fa[u][0]]:u;
	if(son[u])	get_chain(son[u],fa[L][0]);
	for(int i:e[u])
		if(i!=son[u]&&i!=fa[u][0])	get_chain(i,i);
}
inline int ask(int u,int k)//长剖求 k 级祖先
{
	if(!k)	return u;
	u=fa[u][logn[k]];k-=(1<<logn[k]);
	k-=dep[u]-dep[top[u]];u=top[u];
	return k>0?l[dfn[u]+k]:r[dfn[u]-k];
}
ll c[N];//对 dfs 序建立树状数组维护区间加单点查
inline void add(int x,int y){for(;x<=n;x+=x&-x)(c[x]+=y+MOD)%=MOD;}
inline int ask(int x){int s=0;for(;x;x-=x&-x)(s+=c[x])%=MOD;return s;}
int op,u;
signed main()
{
	n=read();q=read();inv=qpow(n,MOD-2);
	for(int i=1,u,v;i<n;i=-~i)
		u=read(),v=read(),
		e[u].push_back(v),e[v].push_back(u);
	for(int i=1;i<=n;i=-~i)
		logn[i]=-~logn[i>>1],siz[i]=1;
	get_son(1);get_chain(1,1);
	while(q--)
	{
		op=read();u=read();
		if(op==1)
		{
			if(!in[u])	v.push_back(u),in[u]=1;
			(d[u]+=read())%=MOD;//将对于同一个点的修改合并
		}
		else
		{
			ll sum=ask(dfn[u]);//前面的块的贡献
			for(int i:v)//当前块的贡献
			{
				if(u==i)	sum+=d[i]*n%MOD;
				else if(dep[u]<=dep[i])	sum+=d[i]*siz[i];//i 子树外
				else
				{
					int x=ask(u,dep[u]-dep[i]-1);
					if(fa[x][0]==i)
						sum+=d[i]*(n-siz[x])%MOD;//i 子树内
					else	sum+=d[i]*siz[i]%MOD;//i 子树外
				}
				sum%=MOD;
			}
			printf("%lld\n",sum*inv%MOD);
		}
		if(v.size()==B)//根号重建
		{
			for(int i:v)
			{
				for(int j:e[i])
					if(j!=fa[i][0])
						add(dfn[j],d[i]*(n-siz[j])%MOD),
						add(dfn[j]+siz[j],-d[i]*(n-siz[j])%MOD);
				add(dfn[i],d[i]*n%MOD);add(-~dfn[i],-d[i]*n%MOD);
				add(1,d[i]*siz[i]%MOD);add(dfn[i],-d[i]*siz[i]%MOD);
				add(dfn[i]+siz[i],d[i]*siz[i]%MOD);
				d[i]=in[i]=0;
			}
			v.clear();
		}
	}
	return 0;
}
posted @ 2024-10-15 19:44  Sorato  阅读(8)  评论(0编辑  收藏  举报