P10879 「KDOI-07」对树链剖分的爱
简要题意
给出一棵 \(n\) 个节点以 \(1\) 为根的有根树。对于第 \(2\le i \le n\) 个节点,其父亲 \(f_i\) 在 [\(l_i\),\(r_i\)] 中均匀随机。每个树的边有边权,初始为 \(0\)。
现在有 \(m\) 次操作,第 \(i\) 次操作表示将 (\(u_i\),\(v_i\)) 的路径上所有的边的权值统一加上 \(w_i\) 。\(m\) 次操作结束后,对于所有 \(i=2\) ∼ \(n\),求 (\(i\),\(f_i\)) 边上权值的期望,对 \(998244353\) 取模。
前置知识
树,期望,dp
思路
非常妙的一道题。
先写一种简单的思路,我们发现如果选择的路径 \((u,v)\) 且 \(u<v\) 因为父亲的编码一定小于自己,所以 \((v,f_v)\) 这条边一定会增加 \(w\) 然后情况就变为处理 \((u,f_v)\) 一个更小的情况。
这启示我们做一个 dp ,设 \(f_{i,j}\) 保证 \(i<j\) 表示(题目给出)初始选择 \((u,v)\) 需要处理 \((i,j)\) 这条链时 \((j,f_j)\) 的期望。转移就非常的简单,\(f_{i,k}+={f_{i,j}\over r[j]-l[j]+1}\) 意思是枚举 \(j\) 的父亲,转移到父亲。\(i\) 的答案显然是 \(\sum_{j<i}f_{j,i}\) 就是枚举所有情况的期望加起来。
但是这样的时间复杂度可以发现是 \(O(n^3)\) 。怎么优化呢?我们发现 \(k\) 的枚举非常的没有用,因为转移与 \(k\) 根本无关。我们观察 \((i,j)\) 转移到的位置,可以发现在二维平面上的两个矩形(可以通过对 \(k\) 与 \(j\) 关系分类即可发现)所以我们做的事情就是矩形加,求单点的值。我们可以通过二维差分变为单点修改求后缀和(因为 \(j\) 影响的是比它小的),然后就非常的简单的变为了 \(O(n^2)\)
代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod=998244353,N=5e3+10;
int n,m,inv[N],f[N][N],ans[N],l[N],r[N];
int add(int u,int v){return (u+v)%mod;}
int sub(int u,int v){return (u-v+mod)%mod;}
void add(int x1,int y1,int x2,int y2,int z)
{
if(x1>y1||x2>y2||x1>x2||y1>y2)return ;
f[x2][y2]=add(f[x2][y2],z);
f[x1-1][y1-1]=add(f[x1-1][y1-1],z);
f[x2][y1-1]=sub(f[x2][y1-1],z);
f[x1-1][y2]=sub(f[x1-1][y2],z);
return ;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=2;i<=n;i++)cin>>l[i]>>r[i];
cin>>m;
for(int i=1;i<=m;i++)
{
int u,v,w;cin>>u>>v>>w;
if(u==v)continue;
if(u>v)swap(u,v);//保证u<v
add(u,v,u,v,w);
}
inv[1]=1;
for(int i=2;i<=n;i++)inv[i]=(mod-mod/i+mod)*inv[mod%i]%mod;//预处理出逆元
for(int u=n;u>=1;u--)
for(int v=n;v>=u;v--)
{
f[u][v]=add(f[u][v],f[u][v+1]);
f[u][v]=add(f[u][v],f[u+1][v]);
f[u][v]=sub(f[u][v],f[u+1][v+1]);//前缀和求出f[u][v]的值
if(u==v)continue;
int val=f[u][v]*inv[r[v]-l[v]+1]%mod;
add(u,max(u+1,l[v]),u,r[v],val);
add(l[v],u,min(u-1,r[v]),u,val);//?
ans[v]=add(ans[v],f[u][v]);
}
for(int i=2;i<=n;i++)cout<<ans[i]%mod<<' ';
return 0;
}

浙公网安备 33010602011771号