AtCoder Grand Contest 013D: Piling Up 题解

本文探讨了一个复杂的动态规划问题,通过引入额外的状态维度来避免重复计数,并提供了详细的转移方程和边界条件处理策略。

非常难的dp

容易想到状态dp[i][j]表示当前进行了i轮操作,第i轮操作结束后盒子里有j个红球的方案数,因为盒子里始终有N个球,所以蓝球的个数为N-j,于是可以转移

但这样有些状态会重复计数

例如:设N=2,则开始状态是Red=2,Blue=0时,可以获得“取出序列”Red,Blue,Red,Blue...

当开始状态是Red=1,Blue=1时,也可以获得“取出序列”Red,Blue,Red,Blue...

我们发现:同一个“取出序列”可以对应多种初始状态,所以有些“取出序列”会被重复计算

一个很好的比喻是,如果将这个“取出序列”在坐标系中化成一条折线,横坐标表示取到第几轮,纵坐标表示盒子里有几个红球

那么同样形状的折线对应的应该是同一种“取出序列”,然而由于折线的出发点不同,即刚开始红球的数量不确定,所以从每个点出发都会有这样一条折线

于是对于同样形状的曲线,我们考虑取其中最特殊的一条:初始状态中红球数最小的一条,即在坐标系中出发点最低的一条

最低的一条应该满足存在一个时刻,红球的数量为0,否则如果红球数量一直大于零,初始状态中的红球一定可以再减少

如下图,两条曲线代表同样的答案被计算多次,我们只计与x轴“相切”的那条


于是有新状态dp[i][j][0/1],第三维为0表示这个序列已经在之前(包括现在)的某时刻红球数为0,第三维为1反之亦然

我们有了正确的状态,但转移也非常难写

一方面要考虑到j=0,j=1,j=n的边界条件

另一方面,红红,红蓝,蓝红,蓝蓝四种转移方法,注意在红蓝和蓝红这两种转移方法中,在转移中间(即拿完红球还没补球时)红球数为0也算与x轴相切

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <cstdlib>
#include <utility>
#include <stack>
#include <queue>
#include <deque>
#include <set>
#include <map>
#include <vector>
#include <cmath>
#define Pair pair<int,int>
#define LOWBIT(x) x & (-x)
#define LL long long
#define mp make_pair
#define pb push_back
#define x first
#define y second
using namespace std;

const int MOD=1e9+7;
const int INF=0x7ffffff;
const int magic=348;
const double eps=1e-9;

//ith step,j red
LL dp[3048][3048][2];
int n,m;

inline LL mod(LL x)
{
	while (x>=MOD) x-=MOD;
	while (x<0) x+=MOD;
	return x;
}

int main ()
{
	int i,j,k;
	scanf("%d%d",&n,&m);
	dp[0][0][0]=1;
	for (i=1;i<=n;i++)
		dp[0][i][1]=1;
	LL ans=0;
	for (i=1;i<=m;i++)
		for (j=0;j<=n;j++)
		{
			if (j==0)
			{
				dp[i][j][0]=mod(dp[i-1][1][0]+dp[i-1][1][1]+dp[i-1][0][0]);
				if (i==m) ans=mod(ans+dp[i][j][0]);
				continue;
			}
			if (j==1)
			{
				dp[i][j][0]=mod((j+1<=n?dp[i-1][j+1][0]:0)+(j==n?dp[i-1][j][0]:2*dp[i-1][j][0])+dp[i-1][j-1][0]+dp[i-1][j][1]);
				if (i==m) ans=mod(ans+dp[i][j][0]);
				dp[i][j][1]=mod((j+1<=n?dp[i-1][j+1][1]:0)+(j==n?0:dp[i-1][j][1]));
				continue;
			}
			dp[i][j][0]=mod((j+1<=n?dp[i-1][j+1][0]:0)+(j==n?dp[i-1][j][0]:2*dp[i-1][j][0])+dp[i-1][j-1][0]);
			dp[i][j][1]=mod((j+1<=n?dp[i-1][j+1][1]:0)+(j==n?dp[i-1][j][1]:2*dp[i-1][j][1])+dp[i-1][j-1][1]);
			if (i==m) ans=mod(ans+dp[i][j][0]);
		}
	printf("%I64d\n",ans);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值