activeO
照彻万川

导航

 

删除背包,非常的牛。

先转化一下题意,要求深度的和,转化为每种方案这个点祖先的个数加 \(1\),就是对于每一组 \((j,i)\)\(j\)\(i\) 的祖先的方案数最后再加一个总方案数。

朴素的就是枚举每个数对 \((i,j)\),使得 \(i\)\(j\) 祖先就要求 \(a_i = \min_{k \in [i,j]}a_k\) ,一种 \(dp\) 方式就是 \(dp_{i,j}\) 表示放了 \(i\) 个数,有 \(j\) 个逆序对的方案,我们只考虑新放的数在前面数中的排名。有方程 \(dp_{i,j}=\sum_{k=0}^{i-1}dp_{i-1,j-k}\)

先放区间内的数,那么就会有 \(j\) 产生的逆序对必须是 \(j-i\),别的数的贡献没有影响,仍然是可以贡献出 \([0,nowi-1]\)。 反之数对是 \((j,i)\) 也同理,总时间复杂度 \(O(n^2\cdot n^3)\)

然后观察一下可以发现对于 \(j-i\) 相同的数对,他的贡献是一样的,进一步,如果对于 \(i_2>j_2,i_2-j_2=j-i\),贡献就取原来的 \(dp_{n,m-(j-i)}\),所以我们改为枚举差值 \(t\),复杂度变为 \(O(n^4)\)

发现每次相当于把原来第 \(t\) 次贡献变为 \(0\)。引入删除背包,对于最终的 \(dp\) 数组进行撤销操作。对于这个一段区间的和的背包可以退回上一步的前缀和数组再进行差分。

// Problem: P5853 [USACO19DEC] Tree Depth P
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P5853
// Memory Limit: 125 MB
// Time Limit: 1000 ms

#include <bits/stdc++.h>

#define int long long

using namespace std;

const int maxn=305;
int dp[3][maxn*maxn],n,m,mod,sum[maxn*maxn];
int f[maxn*maxn],res[maxn];

signed main(){
	
	scanf("%lld %lld %lld",&n,&m,&mod);
	
	dp[0][0]=1;
	for(int i=1;i<=n;i++){
		int now=(i&1);
		dp[now][0]=sum[0]=1;
		for(int j=1;j<=m;j++){
			sum[j]=(sum[j-1]+dp[now^1][j])%mod;
			dp[now][j]=sum[j];
			if(j>=i) dp[now][j]=(dp[now][j]-sum[j-i]+mod)%mod;
		}
	}
	
	for(int t=1;t<n;t++){
		for(int i=0;i<=n*(n-1)/2;i++){
			sum[i]=dp[n&1][i];
			if(i>t) sum[i]=(sum[i]+sum[i-t-1])%mod;
		}
		f[0]=sum[0];
		for(int i=1;i<=n*(n-1)/2;i++) f[i]=(sum[i]-sum[i-1]+mod)%mod;
		for(int i=1;i+t<=n;i++){
			if(m>=t) res[i]=(res[i]+f[m-t])%mod;
			res[i+t]=(res[i+t]+f[m])%mod;
		}
	}
	
	for(int i=1;i<=n;i++) printf("%lld ",(res[i]+dp[n&1][m])%mod);
	
	return 0;
}
posted on 2024-03-29 21:53  activeO  阅读(11)  评论(0编辑  收藏  举报