2019牛客暑期多校训练营(第一场)E.ABBA

https://ac.nowcoder.com/acm/contest/881/E

题意:构造一个长度为2*(n+m)的AB串,使这个串可以划分成n和“AB”的子序列和m个“BA”的子序列,求方案数量

思路:

首先注意:子序列不是子串 只需要相对位置正确 不需要是连续的

确定状态:求什么就设什么,设dp[i][j]为前i个A+前j个B可以组合的合法字符串的数量,那么我们最终要求的dp[n+m][n+m]就是本题的解

对于一个合法的字符串,明确一个性质:

前n个A必定是“AB”的A,同理,可以得到前m个B必定是“BA”的B

可以简单证明一下:如果前n个A里,有一个是“BA”的A,那么这个A后边肯定还存在一个A可以给这个B组合在一起形成“BA”,那么这个A就从原本的“BA”里A变成“AB”里的A,这样就可以得出前n个A都是“AB”的A的结论

之后就是枚举下一个字符是A 还是B

下一个字符放A:

如果i < n,由前n个A必定是“AB”的A可知可以放A

如果i >= n,这个时候就要看B的个数了 ,要确保这个A能给前面的B当"BA"用,那么当前"BA"里需要的A是min(j, m)。所以i - n < min(j,m)成立时,也可以放A。

解释一下为什么是min(j,m), 如果j < m,min(j,m) = j,那么前j个B需要j个A构成“BA”,如果j > m,min(j,m) = m,前m个B都需要m个A构成“BA”。

下一个字符放B:

和放A同理。

如果j < m,由前m个B必定是“BA”的B可知可以放B

如果j >= m,这个时候就要看A的个数了 ,要确保这个B能给前面的A当"AB"用,那么当前"AB"里需要的B是min(i, n)。所以j - m < min(i,n)成立时,也可以放B。

代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <string>
#define ll long long 
#include <set>
using namespace std;
const int Max = 2010;
const  int mod = 1e9+7;
ll n,m;
ll dp[Max][Max];
int main(){
	while(cin>>n>>m){
		int s = n+m;
		for(int i = 0 ; i <= s; i++){
			for(int j = 0 ; j <= s; j++){
				dp[i][j] = 0;
			}
		}
		dp[0][0] = 1;
		for(ll i = 0 ; i <= s; i++){
			for(ll j = 0 ; j <= s; j++){
                                //放A
				if(i < n || (i-n) < min(j,m)){
					dp[i+1][j] = (dp[i+1][j] + dp[i][j])%mod;
				}
                                //放B
				if(j < m || (j-m) < min(i,n)){
					dp[i][j+1] = (dp[i][j+1] + dp[i][j])%mod;
				}
			}
		}
		cout<<dp[s][s]<<"\n";
	}
	return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值