[AT2370] Piling Up
题目大意
一开始有n个颜色为黑白的球,但不知道黑白色分别有多少,m次操作,每次先拿出一个球,再放入黑白球各一个,再拿出一个球,最后拿出的球按顺序排列会形成一个颜色序列,求颜色序列有多少种
n,m小于等于3000
解析
折线法经典题。
我们不妨将黑球的数量当做平面的纵坐标,那么每一次操作的每一步中黑球的数量变化对应的就是折线的上升和下降,每次高度的变化都为1。然而,由于黑球初始的数量不确定,我们求得的折线中有一些是可以通过上下平移得到的,这样的折线其实对应了重复的操作序列。所以,我们强制每一条折线的最低点都在x轴上即可避免重复。
设\(f[i][j][0/1]\)表示操作了\(i\)次、有\(j\)个黑球、是否经过了\(x\)轴(0表示经过,1表示没有)。我们分4种情况讨论DP的转移:
- 两次都取了黑球。对应到折线就是先下降、在上升、在下降,高度减1。
- 两次都取了白球。对应到折线就是上升一次,高度加1。
- 先取黑球再取白球。对应到折线就是先下降再上升,最后高度不变。
- 先去白球再取黑球。对应到折线就是先上升在下降,高度不变。
在转移的过程中注意边界问题,折线移动的过程中必须在\([0,n]\)区间中。
至于第三维的转移,如果是0则只能从0转移过来;如果是1则要么从1转移,要么就是折线移动过程中碰到了\(x\)轴。转移方程比较冗长,具体见代码。
代码
#include <iostream>
#include <cstdio>
#define int long long
#define N 3002
using namespace std;
const int mod=1000000007;
int n,m,i,j,f[N][N][2];
int read()
{
char c=getchar();
int w=0;
while(c<'0'||c>'9') c=getchar();
while(c<='9'&&c>='0'){
w=w*10+c-'0';
c=getchar();
}
return w;
}
signed main()
{
n=read();m=read();
for(i=1;i<=n;i++) f[0][i][0]=1;
f[0][0][1]=1;
for(i=1;i<=m;i++){
for(j=0;j<=n;j++){
if(j<n){
if(j) f[i][j][0]=(f[i][j][0]+f[i-1][j+1][0])%mod;
f[i][j][1]=(f[i][j][1]+f[i-1][j+1][1]+(j==0)*f[i-1][j+1][0])%mod;
}
if(j>0){
if(j!=1) f[i][j][0]=(f[i][j][0]+f[i-1][j-1][0])%mod;
f[i][j][1]=(f[i][j][1]+f[i-1][j-1][1])%mod;
}
if(j<n){
if(j) f[i][j][0]=(f[i][j][0]+f[i-1][j][0])%mod;
f[i][j][1]=(f[i][j][1]+f[i-1][j][1])%mod;
}
if(j>0){
if(j!=1) f[i][j][0]=(f[i][j][0]+f[i-1][j][0])%mod;
f[i][j][1]=(f[i][j][1]+f[i-1][j][1]+(j==1)*f[i-1][j][0])%mod;
}
}
}
int ans=0;
for(i=0;i<=n;i++) ans=(ans+f[m][i][1])%mod;
printf("%lld\n",ans);
return 0;
}