CF2125E Sets of Complementary Sums

很好的 \(DP\)\(trick\) ,值得积累一下。

赛时想到了转化,死于dp计数,悲。

下文中 \(m\) 表示取值范围。

直接刻画 \(Q\) 数组不太好考虑,我们从 \(a\) 数组来考虑,我们不妨先假设 \(a_i\) 互不相同,发现得到的 \(Q\) 数组的差分数组与 \(a\) 数组的差分数组相同,\((S-a_i)-(S-a_j)=(a_i-a_j)\),如果加入了重复的 \(a_i\) 此时 \(Q\) 数组相当于整体加 \(a_i\),注意此时 \(Q\) 数组的差分数组没有改变。所以我们可以通过从所有的 \(Q\) 的本质不同的差分数组来考虑计数,那此时我们找到最小的 \(Q\) 的最小同构(差分数组相同),那此时 \(a\) 数组一定有 \(1\) ,此时 \(Q\) 数组中最大值即为 \(\sum_{i=1}^{n}a_i-1\) 我们从它能得到的数组 \(Q\) 即有 \(m-\sum_{i=1}^{n}a_i+2\) 个,对于所有的这样的 \(a\) 数组贡献累和即为答案。

现在问题转化为从 \(1-m\) 中选 \(n\) 个互不相同的正整数,强制选 \(1\) ,构成序列 \(P\) ,求 \(\sum_P (m-Sum_P+2)\) ,考虑统计选 \(n\) 个数和为 \(p\) 的方案数,设 \(dp_{i,j}\) 表示从一共选了 \(i\) 个数,和为 \(j\) 的方案数,直接转移时间复杂度肯定是不行的。然后就到了神秘 \(trick\) ,我们尽可能多的利用已有状态,对于增加一维 \(dp_{i,j,0/1}\) 代表此时选的数中是否包含 \(1\) ,转移时每次给所有已选的数加 \(1\) 或者加入一个新的数 \(1\),转移时有类似于完全背包的方法把 \(+1\) 拓展为加 \(k\) ,从本质上来看这个 \(dp\) 转移也是在构造差分数组,和本题的转化有异曲同工之妙,这时候第一位大小为 \(\sqrt m\) 级别的,否则答案为 \(0\)。那 \(dp\) 的时间复杂度就为 \(m\sqrt m\) ,利用求出的 \(dp\) 的数组可以简单求出答案。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+10,mod=998244353;
int n,m,dp[N][2];
signed main(){
	int T;cin>>T;
	while(T--){
		cin>>n>>m;
		if(n==1){
			cout<<m<<"\n";
			continue;
		}
		if(n*(n-1)/2>m){
			cout<<"0\n";
			continue;
		}
		for(int i=0;i<=m+1;i++)dp[i][0]=dp[i][1]=0;
		dp[0][0]=1;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m+1;j++)dp[j][1]=dp[j-1][0];
			for(int j=0;j<=m+1;j++){
				if(j<i)dp[j][0]=0;
				else dp[j][0]=(dp[j-i][0]+dp[j-i][1])%mod;
			}
		}
		int ans=0;
		for(int i=1;i<=m+1;i++){
			ans=(ans+dp[i][1]*(m-i+2)%mod)%mod;
		}
		cout<<ans<<"\n";
	}
	return 0;
}
posted @ 2025-07-25 20:43  shanganze  阅读(45)  评论(0)    收藏  举报