CF1499F - Diameter Cut

发布时间 2023-05-07 12:29:10作者: jucason_xu

题意:对于一棵树,有多少种删去边的方式,使得删边之后得到的森林中,每棵树的直径都不超过 \(k\)

见数据范围和直径知 \(dp\),设 \(dp_{i,j}\) 表示当前考虑子树 \(i\),所有直径不大于 \(k\),且从 \(i\) 往下最深深度为 \(j\) 的方案数。

同时注意每棵树转移到祖先的时候,\(j\) 都要自增 \(1\),而 \(dp_{i,0}\) 就变成了所有 \(dp_i\) 的和,因为就是对所有满足条件的 \(i\) 的方案,都把祖先这条边切掉。

下面我们默认转移时使用的 \(dp\) 数组是经过修改的,得到的 \(dp\) 数组是未经过修改的。

然后考虑转移,首先,对于 \(j\le k/2\),我们可以直接转移,因为只要在每个儿子节点都满足 \(j\le k/2\) 即可。也就是 \(\prod_{b\in S_i}\sum_{p=0}^jdp_{b,p}\)。然后这个我们可以预处理每个 \(dp\) 数组的前缀和,求每个 \(j\) 的时候都扫一遍乘起来。因为每个节点会被父亲的每个 \(j\) 扫一次,所以这一步的复杂度是有保证的。

其次我们这里是找到了所有深度不大于 \(j\) 的方案数,要精确得到 \(j\) 的方案数需要做一次差分。

然后,对于 \(j\gt k/2\) 我们注意到只能有一个节点满足深度为 \(j\),那么我们就枚举这个节点 \(x\),并且为了直径合法,其他的选择不能超过 \(k-j\),所以答案就是 \(\sum_{x\in S}\prod_{y\in S,y\neq x}\sum_{p=0}^{k-j}dp_{y,p}\)

显然后面的东西可以用前缀后缀预处理出来,也就是我们可以处理 \(pre_{i,j}\) 表示将前缀的所有 \(sum_{i,j}\) 乘起来,\(suf_{i,j}\) 表示将后缀的所有 \(sum_{i,j}\) 乘起来,然后我们只需要枚举 \(x\),然后把 \(pre_{x-1,k-j}\)\(suf_{x+1,k-j}\) 乘起来,再乘上 \(dp_{x,j}\) 即可。复杂度依旧有保证。

到这里我们就做完了题目,时间复杂度 \(O(nk)\)。但是空间复杂度也是 \(O(nk)\),要开 \(dp,sum,pre,suf\) 四个数组,跑不过去。

首先,习惯乱开 long long 的要关掉,做乘法的时候先乘 1ll 再运算,强转一下死不了人的。这样空间就是 400MB

然后我们发现,\(pre,suf\) 两个数组都只有 \(j\le k/2\) 的部分有效,第二维只开 2500 就够了,300MB

最后,我们发现上述过程,\(dp_j\) 只在第二个转移乘了一次,完全可以现场用 \(sum\) 差分得到。所以我们可以用 \(sum\) 代替 \(dp\) 数组的作用。同时,我们第一类转移本来就是算出的 \(sum\),改成 \(dp\) 本来就是浪费时间,还能优化效率,200MB。到这里就可以过了。

我们再次发现,\(pre\)\(suf\) 没必要对于所有的 \(j\) 都处理出来,只要每次转移 \(dp_{i,j}\) 之前把 \(k-j\) 的先处理一遍就行。而且 \(pre\) 也没必要处理,完全可以一边 \(dp\) 一边处理,这样就只有 100MB 了,稳过,开 long long 也行。


const int P=998244353;
int n,k,dp[5005][5005],a,b;
vt<int>vv[5005];
int lis[5005],sz;
int sum[5005];
int suf[5005];
inline void dfs(int x,int p){
	for(auto j:vv[x])if(j!=p)dfs(j,x);
	sz=0;
	for(auto j:vv[x])if(j!=p)lis[++sz]=j;
	rep(j,0,k/2){
		dp[x][j]=1;
		rep(i,1,sz)dp[x][j]=1ll*dp[x][j]*dp[lis[i]][j]%P;
	}
	rep(j,k/2+1,k){
		dp[x][j]=dp[x][j-1];
		suf[sz+1]=1;
		per(i,1,sz)suf[i]=1ll*suf[i+1]*dp[lis[i]][k-j]%P;
		ll pre=1;
		rep(i,1,sz){
			dp[x][j]=(dp[x][j]+(dp[lis[i]][j]-dp[lis[i]][j-1]+P)%P*1ll*pre%P*suf[i+1]%P)%P;
			pre=1ll*pre*dp[lis[i]][k-j]%P;
		}
	}int sum=dp[x][k];
	per(i,1,k+1)dp[x][i]=(dp[x][i-1]+sum)%P;
	dp[x][0]=sum;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n>>k;
	rp(i,n-1){
		cin>>a>>b;
		vv[a].pb(b);
		vv[b].pb(a);
	}
	dfs(1,0);
	cout<<(dp[1][k+1]-dp[1][0]+P)%P<<endl;
	return 0;
}
//Crayan_r