P2592 [ZJOI2008] 生日聚会 题解 [ 绿 ] [ 前缀和 ] [ 多维 DP ]
生日聚会:在前缀和数组上做多维计数 DP。
没有两球数量之差限制的时候,DP 是显然的,考虑如何处理每个区间的差的绝对值的限制。
注意到我们可以把两种球赋 \(1,-1\) 两种权值,一个区间满足要求,当且仅当区间内数字的和的绝对值 \(\le k\),这样我们就可以快速判断一个区间是否满足要求了。
进而把这个观察加入 DP 中,因为要求区间内数字之和,所以我们从前缀和数组的角度上考虑,一个新状态合法,当且仅当目前状态的前缀和与其余任何前缀和的差的绝对值 \(\le k\) 且旧状态合法。进一步优化,发现我们只要把其余前缀和的最大值和最小值拿出来判断就可以了,如果这两个极值也是满足要求的,剩下没有判断的前缀和也是满足要求的。
又因为 \(k \le 20\),所以就直接做出来了,\(dp_{i,j,l,r}\) 表示放了 \(i\) 个白球,\(j\) 个黑球,其余前缀和的范围是 \([l,r]\) 的方案数,刷表法枚举下一位填 \(0 / 1\) 转移即可。初值设 \(dp_{0,0,0,0}=1\),因为 \([l,r]\) 可能是负数,可以使用 map 存 DP 数组简化代码。注意避免转移无用状态。
时间复杂度 \(O(nmk^2)\)。
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
const int N=155;
const int mod=12345678;
map<pi,int>dp[N][N];
int n,m,x;
ll ans=0;
int main()
{
//freopen("sample.in","r",stdin);
//freopen("sample.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>m>>x;
dp[0][0][{0,0}]=1;
for(int i=0;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
for(int k=-x;k<=x;k++)
{
for(int p=k;p<=x;p++)
{
if(dp[i][j].count({k,p})==0)continue;
int nep=j+1-i;
if(abs(nep-k)<=x&&abs(nep-p)<=x&&j+1<=m)
{
dp[i][j+1][{min(k,nep),max(p,nep)}]=(dp[i][j+1][{min(k,nep),max(p,nep)}]+dp[i][j][{k,p}])%mod;
}
nep=j-(i+1);
if(abs(nep-k)<=x&&abs(nep-p)<=x&&i+1<=n)
{
dp[i+1][j][{min(k,nep),max(p,nep)}]=(dp[i+1][j][{min(k,nep),max(p,nep)}]+dp[i][j][{k,p}])%mod;
}
}
}
}
}
for(int i=-x;i<=x;i++)
{
for(int j=i;j<=x;j++)
{
ans=(ans+dp[n][m][{i,j}])%mod;
}
}
cout<<ans;
return 0;
}

浙公网安备 33010602011771号