动态规划训练10 [Coloring Brackets CodeForces - 149D]

西安交大 软件53 蔡少斐 整理


题目大意:

给定合法的括号序列,让你给括弧上色,并且上色时一定要满足3个要求:

(1)每个括号要么被上红色,要么被上蓝色,要么不上色。

(2)一对匹配的左右括弧,有且只有其中的一个可以被上色。

(3)相邻的括弧不能被涂上相同的颜色。


这道题也是很明显的区间DP问题,遗憾的是,我一开始没有想到怎么dp。


首先我们要进行预处理,求出每个括号的唯一配对的括号,即寻找他们一一对应的关系,这个预处理很简单,用栈操作一下就可以了

预处理代码:

gets(str);
	stack<int> stk;
	int len = strlen(str);
	for(int i = 0;i < len;i++){
		if(str[i] == '('){
			stk.push(i);
		}
		else{
			int p = stk.top();
			stk.pop();
			mp[p] = i;
			mp[i] = p;
		}
	}

下面我们考虑的区间,全部都是配对合法的区间!

用一个四维数组dp[l][r][i][j]表示区间[l,r]且l处被涂上i色,r处被涂上j色。(规定无色为0,红色为1,蓝色为2)

那么我们可以得到下面的状态转移方程:

(1)当区间长度只有2时候(两个括弧一定是配对的,因为我们考虑的所有区间都是配对合法的区间),上色方案是非常好确定的。

dp[l][r][0][1] = 1;
dp[l][r][0][2] = 1;
dp[l][r][1][0] = 1;
dp[l][r][2][0] = 1;

(2)当区间的左右括弧是配对的时候(判断左右括弧是否匹配,用到了预处理得到的结果)。则有如下转移方法:

if(j != 1)
dp[l][r][0][1] = (dp[l][r][0][1] + dp[l+1][r-1][i][j])%MOD;
if(j != 2)
dp[l][r][0][2] = (dp[l][r][0][2] + dp[l+1][r-1][i][j])%MOD;
if(i != 1)
dp[l][r][1][0] = (dp[l][r][1][0] + dp[l+1][r-1][i][j])%MOD;
if(i != 2)
dp[l][r][2][0] = (dp[l][r][2][0] + dp[l+1][r-1][i][j])%MOD;

(3)当区间非左右配对时,把区间划分为左右两个各自合法 的区间,转移方程是。

dp[l][r][i][j] = (dp[l][r][i][j] + dp[l][k][i][p] * dp[k+1][r][q][j]%MOD)%MOD;

这里注意k代表的是与l配对的括号,那么k+1就是与r配对的括号了。(这就是预处理的作用!)


而在动态规划的实现过程中,我们发现并非所有的区间都是合法的,只有少部分的区间是合法的,因此我们用自顶向下的记忆化dp的方法,这样可以简化代码的实现。


代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <stack>
#include <algorithm>
#define int long long 
using namespace std;
const int MAX = 705;
const int MOD = 1e9 + 7;
int used[MAX][MAX];
int mp[MAX];
char str[MAX];
int dp[MAX][MAX][3][3];
void dfs(int l,int r){
	if(used[l][r]) return ;
	used[l][r] = 1;
	if(l + 1 == r){
		dp[l][r][0][1] = 1;
		dp[l][r][0][2] = 1;
		dp[l][r][1][0] = 1;
		dp[l][r][2][0] = 1;
		return ;
	}
	if(mp[l] == r){//配对的情况 
		dfs(l+1,r-1);
		for(int i = 0;i < 3;i++){
			for(int j = 0;j < 3;j++){
				if(j != 1)
					dp[l][r][0][1] = (dp[l][r][0][1] + dp[l+1][r-1][i][j])%MOD;
				if(j != 2)
					dp[l][r][0][2] = (dp[l][r][0][2] + dp[l+1][r-1][i][j])%MOD;
				if(i != 1)
					dp[l][r][1][0] = (dp[l][r][1][0] + dp[l+1][r-1][i][j])%MOD;	
				if(i != 2)
					dp[l][r][2][0] = (dp[l][r][2][0] + dp[l+1][r-1][i][j])%MOD;	
			}
		}
	}
	else{
		int k = mp[l];
		dfs(l,k);
		dfs(k+1,r);
		for(int i = 0;i < 3;i++){
			for(int j = 0;j < 3;j++){
				for(int p = 0;p < 3;p++){
					for(int q = 0;q < 3;q++){
						if(p + q == 0 || p != q ){
							dp[l][r][i][j] = (dp[l][r][i][j] + dp[l][k][i][p] * dp[k+1][r][q][j]%MOD)%MOD;
						}
					}
				} 
			}
			
		} 
		 
	}
}
main(){
	gets(str);
	stack<int> stk;
	int len = strlen(str);
	for(int i = 0;i < len;i++){
		if(str[i] == '('){
			stk.push(i);
		}
		else{
			int p = stk.top();
			stk.pop();
			mp[p] = i;
			mp[i] = p;
		}
	}
	dfs(0,len-1);
	int ans = 0;
	for(int i = 0;i < 3;i++){
		for(int j = 0;j < 3;j++){
			ans = (ans + dp[0][len-1][i][j])%MOD;
		}
	}
	cout<<ans<<endl;
	return 0;
} 


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值