Nephren 题解
我们先考虑已经切断若干边后的情况,这时树会分裂成若干子树。
每个子树也能类似地定义其战斗力为子树内防守区块战斗力之和,而基地的总战斗力就是每个子树的战斗力之和。
我们可以发现,每个子树
由期望的线性性,考虑对每个子树单独计算战斗力期望。
于是我们需要考虑如何计算子树
这里有一个很巧妙的结论:
首先在一个有
于是我们就能根据期望的性质得到:
于是我们发现
所以我们有
其中
现在我们考虑怎么求对于所有分割方案,上述式子的期望。
我们可以退而求其次,考虑求解上述式子对于所有分割方案的和,然后除以总方案数得到期望。
于是我们可以得到一个显然的 dp:设
如果要做到线性,我们需要用一个树上连通块 dp 的组合意义优化:
考虑将上式改写为:
于是我们可以通过组合意义写出定义 dp 方程:
对于第一部分
每个连通块
子树内:
对于
如果不在同一连通块内,那么
子树外:
可以发现子树外的贡献对于转移过程没有影响,因此统计答案时乘上即可。
最终的答案为
拓展
通过这种方式,我们可以在
代码
#include <bits/stdc++.h>
using namespace std;
#define N 1000005
int n,hd[N],t,d[N],p[N];
typedef long long ll;
ll f[N][3],g[N][2],sz[N],ans,pow2[N];
#define MOD 998244353
struct edge{
int u,v,nxt;
}es[2*N];
void add_edge(int u,int v){
es[++t]=(edge){u,v,hd[u]};
hd[u]=t;
}
ll fastpow(ll x,int p){
ll res=1;
while(p>0){
if(p&1)res = res*x%MOD;
p>>=1;
x=x*x%MOD;
}
return res;
}
void dfs(int u,int fa){
f[u][0]=1;f[u][1]=1;
g[u][0]=1;g[u][1]=1;
sz[u]=1;
int v;
for(int i=hd[u];i;i=es[i].nxt){
v = es[i].v;
if(v==fa)continue;
dfs(v,u);
sz[u]+=sz[v];
//C(k,2)
f[u][2]=(f[u][2]*f[v][0]+f[u][1]*f[v][1]+f[u][0]*f[v][2]//同一连通块
+f[u][2]*pow2[sz[v]-1])%MOD;//不同连通块
f[u][1]=(f[u][1]*f[v][0]+f[u][0]*f[v][1]//同一连通块
+f[u][1]*pow2[sz[v]-1])%MOD;//不同联通块
f[u][0]=(f[u][0]*f[v][0]//同一连通块
+f[u][0]*pow2[sz[v]-1])%MOD;//不同连通块
//C(k,1)
g[u][1]=(g[u][1]*g[v][0]+g[u][0]*g[v][1]//同一连通块
+g[u][1]*pow2[sz[v]-1])%MOD;//不同连通块
g[u][0]=(g[u][0]*g[v][0]//同一连通块
+g[u][0]*pow2[sz[v]-1])%MOD;//不同连通块
}
ans = (ans+f[u][2]*pow2[(n-sz[u]-1)>=0?n-sz[u]-1:0]+g[u][1]*pow2[(n-sz[u]-1)>=0?n-sz[u]-1:0])%MOD;
}
ll rev(ll x){
return fastpow(x,MOD-2);
}
int main(){
cin >> n;
int u,v;
pow2[0]=1;
for(int i=1;i<=n;i++)pow2[i]=pow2[i-1]*2%MOD;
for(int i=1;i<n;i++){
scanf("%d%d",&u,&v);
add_edge(u,v);
add_edge(v,u);
}
dfs(1,0);
cout << ans << endl;
printf("%lld",(ans*rev(pow2[n])%MOD));
return 0;
}
后记
在出数据的时候爆了本地栈qwq