Reunion
题解
这道题如果直接去求每个状况的树的最大等级明显是很麻烦的,我们可以先考虑求出对于
i
i
i,有多少个树拥有半径大于
i
i
i的块。
很明显,如果我们将每个等级满足条件的树的数量加起来,是等于所有树的等级之和的。
对于等级的处理,我们可以转化成最近的未参加点的距离。
可怎么求满足条件的树的数量呢?我们可以考虑容斥,先求出不满足条件的树的数量,再去相减。
记
d
p
i
,
j
dp_{i,j}
dpi,j表示从点
i
i
i出发,到子树内最近的未参加的点的距离为
j
j
j的子树的数量。
为方便起见我们再跳到下一个节点时再将该点的状态算入其中,先将每个儿子的dp值依次合并到父亲上,那么再将
j
j
j合并到
i
i
i上,当
i
<
k
i< k
i<k时,有,
d
p
u
,
i
=
d
p
u
,
i
∑
j
=
i
2
∗
k
−
i
d
p
v
,
j
+
d
p
v
,
i
∑
j
=
i
+
1
2
∗
k
−
i
d
p
u
,
i
dp_{u,i}=dp_{u,i}\sum_{j=i}^{2*k-i}dp_{v,j}+dp_{v,i}\sum_{j=i+1}^{2*k-i}dp_{u,i}
dpu,i=dpu,i∑j=i2∗k−idpv,j+dpv,i∑j=i+12∗k−idpu,i
k
k
k表示我们正在枚举半径为
k
k
k时的状态。
但当我们枚举
k
⩽
i
⩽
2
k
k\leqslant i \leqslant 2k
k⩽i⩽2k时,枚举的有些区别,这里主要枚举的是中心不在
u
u
u的情况,有
d
p
u
,
i
=
d
p
u
,
i
∑
j
=
2
∗
k
−
i
i
d
p
v
,
j
+
d
p
v
,
i
∑
j
=
2
∗
k
−
i
i
−
1
d
p
u
,
j
dp_{u,i}=dp_{u,i}\sum_{j=2*k-i}^{i}dp_{v,j}+dp_{v,i}\sum_{j=2*k-i}^{i-1}dp_{u,j}
dpu,i=dpu,i∑j=2∗k−iidpv,j+dpv,i∑j=2∗k−ii−1dpu,j
往上传的时候注意将所有的
d
p
dp
dp值都统计一下,表示当前点未参加的方案数,再将其他的dp值往后移动一位,方便下一个点的转移。
时间复杂度 O ( n 3 ) O\left(n^3\right) O(n3)。
源码
#include<bits/stdc++.h>
using namespace std;
#define MAXN 100005
#define lowbit(x) (x&-x)
#define reg register
typedef long long LL;
typedef unsigned long long uLL;
typedef unsigned int uint;
typedef pair<int,int> pii;
const LL INF=0x7f7f7f7f7f7f;
const int mo=998244353;
const double PI=acos(-1.0);
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
_T f=1;x=0;char s=getchar();
while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
x*=f;
}
int n,head[MAXN],tot,k,pow2[MAXN],dp[305][605],tmp[605],sumx[605],sumy[605],ans;
struct edge{int to,nxt;}e[MAXN];
void addEdge(int u,int v){e[++tot]=(edge){v,head[u]};head[u]=tot;}
int add(int x,int y){return x+y<mo?x+y:x+y-mo;}
int qkpow(int a,int s){int t=1;while(s){if(s&1)t=1ll*a*t%mo;a=1ll*a*a%mo;s>>=1;}return t;}
void merge(int x,int y){
sumx[0]=dp[x][0];for(int i=1;i<=2*k;i++)sumx[i]=add(sumx[i-1],dp[x][i]);
sumy[0]=dp[y][0];for(int i=1;i<=2*k;i++)sumy[i]=add(sumy[i-1],dp[y][i]);
for(int i=0;i<k;i++){
tmp[i]=add(tmp[i],1ll*dp[x][i]*sumy[2*k-i-1]%mo);
tmp[i]=add(tmp[i],1ll*dp[y][i]*sumx[2*k-i-1]%mo);
if(i)tmp[i]=add(tmp[i],mo-1ll*dp[x][i]*sumy[i-1]%mo);
if(i)tmp[i]=add(tmp[i],mo-1ll*dp[y][i]*sumx[i-1]%mo);
tmp[i]=add(tmp[i],mo-1ll*dp[x][i]*dp[y][i]%mo);
}
for(int i=k;i<=2*k;i++){
tmp[i]=add(tmp[i],1ll*dp[x][i]*sumy[i]%mo);
tmp[i]=add(tmp[i],1ll*dp[y][i]*sumx[i]%mo);
if(i<2*k)tmp[i]=add(tmp[i],mo-1ll*dp[x][i]*sumy[2*k-i-1]%mo);
if(i<2*k)tmp[i]=add(tmp[i],mo-1ll*dp[y][i]*sumx[2*k-i-1]%mo);
tmp[i]=add(tmp[i],mo-1ll*dp[x][i]*dp[y][i]%mo);
}
for(int i=0;i<=2*k;i++)dp[x][i]=tmp[i],tmp[i]=sumx[i]=sumy[i]=0;
}
void dosaka(int u,int fa){
dp[u][k]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;if(v==fa)continue;
dosaka(v,u);merge(u,v);
}
int sum=0;
for(int i=0;i<=2*k;i++)printf("dp%d %d %d:%d\n",u,k,i,dp[u][i]);
for(int i=2*k;i;i--)sum=add(sum,dp[u][i]),dp[u][i]=dp[u][i-1];
dp[u][0]=add(dp[u][0],sum);
}
signed main(){
read(n);pow2[0]=1;for(int i=1;i<=n;i++)pow2[i]=add(pow2[i-1],pow2[i-1]);
for(int i=1,u,v;i<n;i++)read(u),read(v),addEdge(u,v),addEdge(v,u);
for(k=1;k<=n;k++){
for(int i=1;i<=n;i++)for(int j=0;j<=2*k;j++)dp[i][j]=0;dosaka(1,0);
int res=pow2[n];for(int i=0;i<=k;i++)res=add(res,mo-dp[1][i]);ans=add(ans,res);
}
ans=1ll*qkpow(pow2[n],mo-2)*add(ans,mo-1)%mo;
printf("%d\n",ans);
return 0;
}
/*
3/4*1+1/4*3
*/
谢谢!!!
关于这篇文章,以后还会再更新一下,笔者还有些地方没学懂。