[CF1517F]Reunion

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,ij=i2kidpv,j+dpv,ij=i+12kidpu,i
k k k表示我们正在枚举半径为 k k k时的状态。
但当我们枚举 k ⩽ i ⩽ 2 k k\leqslant i \leqslant 2k ki2k时,枚举的有些区别,这里主要枚举的是中心不在 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,ij=2kiidpv,j+dpv,ij=2kii1dpu,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
*/

谢谢!!!

关于这篇文章,以后还会再更新一下,笔者还有些地方没学懂。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值