AGC013D题解

做题思路

简单想法

根据题意可以得出显而易见的结论:

在每次操作完成后,白球有 (x) 个时,黑球数量为 (n-x) 。于是我们设白球数量为 (x) 个。

对于本题的操作,仅有以下四种:

条件 (xge 1) :

  • 取出一个白球,加入一对黑白球,取出一个白球
  • 取出一个白球,加入一对黑白球,取出一个黑球

条件 (x<n) :

  • 取出一个黑球,加入一对黑白球,取出一个白球
  • 取出一个黑球,加入一对黑白球,取出一个黑球

那么我们可以想到一个简单的 DP :

(f_{i,j}) 为进行到操作 i 还剩 j 个白球的操作方案数,初始状态为

(f_{0,j}=1 , jin[0,n])

根据题意我们可以得到状态转移方程:

if(j>=1)
	f[i+1][j-1]=f[i+1][j-1]+f[i][j],
	f[i+1][j]=f[i+1][j]+f[i][j];
if(j<n)
	f[i+1][j]=f[i+1][j]+f[i][j],
	f[i+1][j+1]=f[i+1][j+1]+f[i][j];

但是这样转移显然有问题,因为当初始时白球数量不同时,进行相同的操作会得到相同的序列,而这种方案被我们进行了重复计算。

完善想法

那么问题变为了如何去除同一方案的重复贡献。

为了方便,我们将每次操作前后白球的数量变化画为折线,那么上文提到的操作可以用折线所表示:

那么假设我们对某种初始情况进行了操作 114314 (假设初始时白球数量为5),我们得到了以下折线:

显然,初始白球数量不同(折线起点纵坐标不同)且操作合法(操作期间白球数量(num_{white}in [0,n])) 我们在 DP 时发生了重复统计,反映到折线上(假设 (n=6) )就如下图所示:

每一条折线所产生的颜色序列完全相同,但显然被统计了多次。

于是,我们产生了一个想法,对于每一种折线,只标记其中的一条,只统计它自己的贡献就行了。那么标记哪一条折线比较方便呢?

继续看上面的图,可以发现这些形状相同的折线唯一的不同点就是值域不同,于是就产生了一个做法: 我们只统计那些触碰到边界(与x轴接触的到折线)。

因为对于每一种形态且合法的折线,由于不同的起始点,所经历的纵坐标值域不同,仅有一条触碰到了x轴,其他折线均可以这条折线向上平移若干单位得到。

那么,我们可以将 DP 数组 (f_{i,j}) 多开一维变成 (f_{i,j,k}) ,(k)这一维表示这条折线是否接触到x轴,最后我们只需要统计 (sum^{n}_{j=0}f_{m,j,1}) 就行了

初始状态为 (f_{0,j,0}=1,jin [0,n] and f_{0,0,1}=1).

还有一点要注意的就是 j=1 时进行操作二时,折线其实是会接触到x轴的(第一步拿走一个白球会使白球数为0)

那么我们就能 偷税地 DP 了,

Code

#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 3005
#define LL long long 
using namespace std;

int n,m,f[N][N][2],ans;
const int mod = 1000000007;

inline int qr()
{
    int x=0,w=1;char ch=0;
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    return x*w;
}

inline int add(int a,int b){return a+b>mod?a+b-mod:a+b;}//让加法快一点(可能)

int main()
{
	n=qr(),m=qr();
	f[0][0][1]=1;   //初始化部份
	for(register int j=1;j<=n;j++) f[0][j][0]=1;
	for(register int i=0;i<m;i++)        //向后推操作
		for(register int j=0;j<=n;j++)   //枚举剩下多少个白球
		{
			if(j-1>=0)
			{
				//如果j==1时进行操作一和二会使折线接触到x轴(白球数变成0)
				j==1 ? f[i+1][j-1][1]=add(f[i+1][j-1][1],f[i][j][0]) : f[i+1][j-1][0]=add(f[i+1][j-1][0],f[i][j][0]);//操作一
				f[i+1][j-1][1]=add(f[i+1][j-1][1],f[i][j][1]);
				j==1 ? f[i+1][j][1]=add(f[i+1][j][1],f[i][j][0]) : f[i+1][j][0]=add(f[i+1][j][0],f[i][j][0]);//操作二
				f[i+1][j][1]=add(f[i+1][j][1],f[i][j][1]);
			}
			if(j+1<=n)
			{
				f[i+1][j+1][1]=add(f[i+1][j+1][1],f[i][j][1]);//操作三
				f[i+1][j+1][0]=add(f[i+1][j+1][0],f[i][j][0]);
				f[i+1][j][1]=add(f[i+1][j][1],f[i][j][1]);//操作四
				f[i+1][j][0]=add(f[i+1][j][0],f[i][j][0]);
			}
		}
	for(register int j=0;j<=n;j++)//累加答案
		ans=add(ans,f[m][j][1]);
	printf("%d
",ans);
    return 0;
}
原文地址:https://www.cnblogs.com/isonder/p/14613803.html