【CF1845E】Boxes and Balls

发布时间 2023-07-04 22:14:57作者: stoorz

题目

题目链接: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;
}