AtCoder agc013_d - Piling Up
铜题………………(orz tzc没看题解AC了)
洛谷题目页面传送门 & AtC题目页面传送门
题意见洛谷。
首先探索一个颜色序列的合法性。容易发现,合法当且仅当拿出每个球后,当前状态合法,即两种颜色的球的数量都非负。我们可以把盒子里的球的数量的每次变化都给整理出来(只需要实时记录红球(蓝球也可)的数量\(cnt\)即可,蓝球的数量就用总数减掉)。
每一次操作一共分为三步:
- 拿出一个球,若是红球则减一,否则不变。此时总数为\(n-1\),要满足\(cnt\geq0,n-1-cnt\geq0\),即\(cnt\in[0,n-1]\);
- 放入一红一蓝,\(cnt\)加一。此时不用满足啥,上一步合法这一步肯定也合法,所以归到下一步里一起结算;
- 拿出一个球,和上一步合起来的话,若是红球则不变,否则加一。此时总数为\(n\),要满足\(cnt\geq0,n-cnt\geq0\),即\(cnt\in[0,n]\)。
不难想到,可以将这个变化过程做成一个折线,那么在\(x\)轴(时间轴)的每一处都有一个上下界,然后数合法的折线个数即可。这个很容易DP,设\(dp_{i,j}\)为到第\(i\)步(一共\(2m\)步),当前的\(y\)值为\(j\)时合法折线数。目标:\(\sum\limits_{i=0}^ndp_{2m,i}\);由于起点(初始球数量分布情况)不定,所以边界是\(\forall i\in[0,n],dp_{0,i}=1\)。转移的话就很简单了,每一步的转移量都是常数级的。时间复杂度\(\mathrm O(nm)\)。
然后你认为,作为一道铜题,怎么可能这么水?于是带到第一个样例里手玩了一下,发现WA了……然后你就发现,上面那句“数合法的折线个数即可”就是在xjb扯淡。当两条折线起点不同,但是每一处的增长量都相同,即上下平移可重合时,它们代表的颜色序列是相同的。考虑去重。
去重有一个惯用套路(不知为啥啥都可以被我总结成套路/yiw),就是把一些重复答案的不同处给限定一下,使得每个重复类里恰好有一个满足那个限定。本题中,限定方法非常显然,对于每个重复类,我们把每条折线都往下平移,直到接触到\(x\)轴了,再也不能往下平移了(因为每处的下界都是一样的,都是\(0\)),显然重复类里的每个折线都重合。如此一来,加上一个“接触\(x\)轴”这个限定,就完美去重了。
如何实现?只需要在DP上加上第三维\(k\in\{0,1\}\),表示到目前为止是否已经接触\(x\)轴。目标:\(\sum\limits_{i=0}^ndp_{2m,i,1}\),边界:\(\forall i\in[0,n],dp_{0,i,[i=0]}=1\)。转移依然xjb转移。
代码:
#include<bits/stdc++.h>
using namespace std;
const int mod=1000000007;
const int N=3000,M=3000;
int n,m;
int dp[2*M+1][N+1][2];
int main(){
cin>>n>>m;
for(int i=0;i<=m;i++)dp[0][i][i==0]=1;//边界
for(int i=1;i<=m;i++)for(int j=0;j<=n;j++){//转移
if(j==0){
(dp[2*i-1][j][1]+=dp[2*i-2][j][1])%=mod;
(dp[2*i-1][j][1]+=(dp[2*i-2][j+1][0]+dp[2*i-2][j+1][1])%mod)%=mod;
(dp[2*i][j][1]+=dp[2*i-1][j][1])%=mod;
}
else{
if(j<n){
(dp[2*i-1][j][0]+=dp[2*i-2][j][0])%=mod,(dp[2*i-1][j][1]+=dp[2*i-2][j][1])%=mod;
(dp[2*i-1][j][0]+=dp[2*i-2][j+1][0])%=mod,(dp[2*i-1][j][1]+=dp[2*i-2][j+1][1])%=mod;
}
(dp[2*i][j][0]+=dp[2*i-1][j][0])%=mod,(dp[2*i][j][1]+=dp[2*i-1][j][1])%=mod;
(dp[2*i][j][0]+=dp[2*i-1][j-1][0])%=mod,(dp[2*i][j][1]+=dp[2*i-1][j-1][1])%=mod;
}
}
// for(int j=n;~j;j--){for(int i=0;i<=2*m;i++)printf("(%2d,%2d) ",dp[i][j][0],dp[i][j][1]);puts("");}
int ans=0;
for(int i=0;i<=n;i++)(ans+=dp[2*m][i][1])%=mod;//目标
cout<<ans;
return 0;
}