AGC013 D Piling Up dp

版权声明:本文为博主原创文章,可以转载但是必须声明版权。 https://blog.csdn.net/forever_shi/article/details/88558235

题目链接

题意:
一开始有 n n 个颜色为黑色或白色的球,但不知道黑色白色球分别有多少。有 m m 次操作,每次先拿出一个球,再放入黑白球各一个,再拿出一个球,最后拿出的球按顺序排列会形成一个颜色序列,求颜色序列有多少种。对 1 e 9 + 7 1e9+7 取模。 n , m < = 3000 n,m<=3000

题解:
直觉上就是个 O ( n m ) O(nm) 的dp吧。但是具体怎么设状态,是这个题的关键。

我们发现这个题比较容易想到的很多状态设计方法要么会算重,要么会算漏,要么根本就很难计算答案。

显然可以发现,每次操作完了之后我们的一开始的一个考虑是,我们设 d p [ i ] [ j ] dp[i][j] 表示前 i i 次操作,箱子里有 j j 个黑球的方案数,这个直接dp显然是会算重的,但是我们考虑有没有能避免重复的情况。我们考虑在拿球的过程中,每次拿完黑球的数量可能+1,-1或者不变,我们发现,只有在某个时刻黑球数变为0了,才会出现其他情况出现不了的方案。原因是我们可以把每次操作后的黑球数量的变化情况看作一条折线,那么如果一条线没有接触到0的话,我们可以看作一开始黑球数比它少一些的某个情况的线往上平移得到,而这两条线可以由平移得到,说明球的序列是一样的,本质是同一种方案。综上,我们只需要考虑所有出现过黑球数量是0的情况的方案数即可。

那么我们设出新的dp状态,我们设 d p [ i ] [ j ] [ 0 / 1 ] dp[i][j][0/1] 表示 i i 次操作,现在箱子有 j j 个黑球,是否出现过黑球数量是0的时刻。转移的话要分好多边界情况讨论,转移的具体式子可以看代码,我就不写了,但是说一下转移的情况。四种情况分别是这次操作取了两个黑球、取了两个白球、先选了一个黑球后选了一个白球、先选了一个白球后选了一个黑球。注意最后两种是不同的,因为你先选的球的颜色决定着你转移过来的上一个状态是有几个黑球,两种情况一种是需要之前有黑球的,一种是不需要的。

代码:

#include <bits/stdc++.h>
using namespace std;

int n,m;
long long ans,dp[3010][3010][2];
const long long mod=1e9+7;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
	dp[0][i][0]=1;
	dp[0][0][1]=1;
	for(int i=1;i<=m;++i)
	{
		for(int j=0;j<=n;++j)
		{
			if(j>=1)
			{
				dp[i][j-1][1]=(dp[i][j-1][1]+dp[i-1][j][1])%mod;
				dp[i][j][1]=(dp[i][j][1]+dp[i-1][j][1])%mod;
			}
			if(j>=2)
			{
				dp[i][j-1][0]=(dp[i][j-1][0]+dp[i-1][j][0])%mod;
				dp[i][j][0]=(dp[i][j][0]+dp[i-1][j][0])%mod;
			}
			if(j==1)
			{
				dp[i][j-1][1]=(dp[i][j-1][1]+dp[i-1][j][0])%mod;
				dp[i][j][1]=(dp[i][j][1]+dp[i-1][j][0])%mod;
			}
			if(j<n)
			{
				dp[i][j+1][0]=(dp[i][j+1][0]+dp[i-1][j][0])%mod;
				dp[i][j+1][1]=(dp[i][j+1][1]+dp[i-1][j][1])%mod;
				dp[i][j][0]=(dp[i][j][0]+dp[i-1][j][0])%mod;
				dp[i][j][1]=(dp[i][j][1]+dp[i-1][j][1])%mod;
			}
		}
	}
	for(int i=0;i<=n;++i)
	ans=(ans+dp[m][i][1])%mod;
	printf("%lld\n",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/forever_shi/article/details/88558235