【CF1845E】Boxes and Balls
题目
题目链接:https://codeforces.com/problemset/problem/1845/E
\(n\) 个盒子排成一排,每个盒子里可能会有 \(0\) 或 \(1\) 个球,一次操作可以把一个球移动到相邻的盒子中,并且要求任何时刻不能存在一个盒子装有两个球。
给定 \(m\),求在恰好 \(m\) 次操作后有多少种可能的情况。
\(n,m\leq 1500\)。
思路
假设初始情况对应的 \(01\) 序列是 \(s\),若干次操作后的情况为 \(t\)。那么 \(s\) 能通过恰好 \(k\) 次操作变为 \(t\),当且仅当两个序列中对应的 \(1\) 的位置差之和 \(\leq k\) 且奇偶性和 \(k\) 一致。
朴素的 dp 可以设 \(f[i][j][k]\) 表示前 \(i\) 个盒子内装了 \(j\) 个球,对应的球的位置差之和为 \(k\) 的方案数。时间复杂度 \(O(n^2m)\)。
这样并不好优化。发现 \(s\) 转移到 \(t\) 的最小操作次数等于每一个盒子被操作的次数之和,而第 \(i\) 个盒子被操作的次数,恰好是 \(s[1\sim i]\) 中 \(1\) 的数量与 \(t[1\sim i]\) 中 \(1\) 的数量之差的绝对值。
那么可以设 \(f[i][j][k]\) 表示考虑到第 \(i\) 个盒子的贡献,其中 \(s[1\sim i]\) 与 \(t[1\sim i]\) 的 \(1\) 的数量差(不加绝对值)为 \(j\),且前 \(i\) 个盒子的贡献和为 \(k\) 的方案数。
- 如果不在第 \(i\) 个盒子内放球,那么 \(f[i-1][j][k]\) 就可以转移到 \(f[i][j-s_i][k+|j-s_i|]\)。
- 如果在第 \(i\) 个盒子内放一个球,那么 \(f[i-1][j][k]\) 就可以转移到 \(f[i][j+1-s_i][k+|j+1-s_i|]\)。
时间复杂度仍是 \(O(n^2m)\)。
但是注意到最终的答案是 \(\sum^{m}_{i=1}f[n][0][i]\times [i\bmod 2=m\bmod 2]\),而第二维的 \(j\) 在从 \(i\) 转移到 \(i+1\) 时,最多只能变化 \(1\),为了让最后第二维能回到 \(0\),那么第二维的最大值 \(lim\) 应该满足 \(\sum^{lim}_{i=1}i\leq m\),也就是说,第二维其实可以被压缩至 \(O(\sqrt m)\) 级别。时间复杂度 \(O(nm\sqrt m)\)。
记得滚动数组。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1510,M=55,MOD=1e9+7;
int n,m,ans,a[N],f[2][M*2][N];
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
scanf("%d",&a[i]);
f[0][M][0]=1;
for (int i=1;i<=n;i++)
{
int id=i&1;
memset(f[id],0,sizeof(f[id]));
for (int j=-M+2;j<=M-2;j++)
for (int k=0;k<=m;k++)
{
int x=j-a[i],y=j-a[i]+1;
if (k+abs(x)<=m) f[id][x+M][k+abs(x)]=(f[id][x+M][k+abs(x)]+f[id^1][j+M][k])%MOD;
if (k+abs(y)<=m) f[id][y+M][k+abs(y)]=(f[id][y+M][k+abs(y)]+f[id^1][j+M][k])%MOD;
}
}
for (int i=m;i>=0;i-=2)
ans=(ans+f[n&1][M][i])%MOD;
printf("%d",ans);
return 0;
}