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;
}

浙公网安备 33010602011771号