CF1254D Tree Queries 题解
CF1254D Tree Queries 题解
题目大意#
给定一棵 个节点的树,有 次操作,分别是如下两种:
- 给定一个点 和一个权值 ,等概率地选择一个点 ,对每一个点 ,若 在 到 的路径上,则的权值加上。
- 查询 的权值期望,对 取模。
Solve#
考虑对节点 执行操作 1 之后,对树上所有节点的贡献。
- 对于 自己,期望权值需要加上 。
- 对于 子树里(除去 自己)的点,若这个点在以 为根的子树里(),那么只有选到的 不在 的子树里才对这个点有贡献,所以这个点的期望权值需要加上 。
- 对于不在 子树里的点,需要选到的 在 子树里,才能对这个点有贡献,期望权值需要加上 。
对树建立 dfs 序,那么就可以转化为区间加,用树状数组维护。
但问题是,对 子树里的点的贡献和那个点所在的子树有关,如果每次都遍历 的所有儿子,可以被菊花卡到 。所以考虑一个根号分治 / 根号重建。
将操作分为若干个大小为 的块。记录每个块内的修改操作,把对同一个节点的修改合并。
每 次修改,遍历这个块内的所有修改并按上面的方式执行。由于每个点的所有边至多被遍历一次,复杂度 。
对于一次询问,它所在块前面的贡献都已经通过数据结构统计出来了,只需要遍历这个块里的所有修改就行。
比如当前询问的点是 ,遍历到的修改是 。还是按上面所说的分类讨论计算贡献。那么我们需要快速求出 是否在 的子树里,若在,是在 的哪个儿子的子树里。可以分情况讨论。
- 若 ,特判。
- 否则若 的深度 ,说明 肯定不在 的子树里。
- 若 ,我们对 求 级祖先,记为 。如果 的父亲不是 ,说明 不在 的子树里。否则, 在 的子树里,并且我们也已求得了它是在 的哪个儿子的子树里。
这样,就可以计算贡献了。时间复杂度为 ,其中 为求 级祖先的复杂度消耗。
对于求 级祖先,方法很多。由于我们对于不同块的操作已经带了一个 ,如果我们在这里还使用带 的算法求,如树剖和倍增,那么总复杂度是在 时取得最小值,为 ,较慢。实测倍增写法跑了 4000ms+。
用长链剖分求 级祖先,可以做到 。这样总复杂度在 时取得最小值 。
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
2023-10-15 P9310 Luna likes Love 题解