【DP-杂题】LOJ2713 [BalkanOI 2018 Day2] Parentrises

【题目】
LOJ
一个括号序列是合法的当且仅当它左右括号能一一匹配。称一个括号串是好的,当且仅当将它每个括号染成 RGB \text{RGB} RGB三种颜色后,忽略所有的 R \text{R} R括号或 B \text{B} B括号后它都合法。

有两种任务

  • P = 1 P=1 P=1,问一个括号串是否是好的,若是,给出一种染色方案。
  • P = 2 P=2 P=2,问有多少个长度为 n n n的不同的好的括号串。

均为多组数据,当 P = 1 P=1 P=1,字符串总长不超过 1 0 6 10^6 106。当 P = 2 P=2 P=2 T , n ≤ 300 T,n\leq 300 T,n300

【解题思路】
如何判断一个字符串是不是合法的?我们先将左括号看作 + 1 +1 +1,右括号看作 − 1 -1 1

不妨考虑这样一种 DP \text{DP} DP:设 f i , s 1 , s 2 f_{i,s_1,s_2} fi,s1,s2表示前 i i i个括号, s 1 s_1 s1为忽略蓝色括号的前缀和, s 2 s_2 s2为忽略红色括号的前缀和,是否可达。转移显然,这样我们只需要看最后 f n , 0 , 0 f_{n,0,0} fn,0,0是否为真即可。

看这个 P = 1 P=1 P=1 DP \text{DP} DP,由于我们现在只考虑是否可达,不妨观察一下转移方式。我们将整个状态可达表看作一个网格图,横坐标表示 s 1 s_1 s1,纵坐标表示 s 2 s_2 s2,可以发现,可达的区域始终有两条对角线(左上到右下)的上下界。具体来说,我们设下边界为 d 1 d_1 d1,上边界为 d 2 d_2 d2,当遇到一个左括号, d 1 + = 1 , d 2 + = 2 d_1+=1,d_2+=2 d1+=1,d2+=2,当遇到一个右括号, d 1 − = 2 , d 2 − = 1 d_1-=2,d_2-=1 d1=2,d2=1。对于边界的长度,下边界始终为满,上边界每遇到一个右括号会 + 1 +1 +1,初始为 1 1 1。当然观察到只需要看最终 ( 0 , 0 ) (0,0) (0,0)的位置是否可达,因此我们实际上不需要考虑长度:在执行过程中,若 d 1 &lt; 0 d_1&lt;0 d1<0,则将 d 1 = 0 d_1=0 d1=0,若 d 2 &lt; 0 d_2&lt;0 d2<0,则没有合法方案。若最终 d 1 &gt; 0 d_1&gt;0 d1>0,则无合法方案。

我们现在已经可以在 O ( n ) O(n) O(n)的时间内判断它是否合法,那么如何构造一种情况?考虑到我们已经知道了最终答案的可行范围,那么实际上我们可以在始终沿着斜线走的基础上,再通过上下和左右的移动来构造出。具体实现可以见代码。

这部分的复杂度就是 O ( n ) O(n) O(n)的了。

再看 P = 2 P=2 P=2的部分,观察到转移也是按斜线贡献的,那么实际上我们设 f i , j , k f_{i,j,k} fi,j,k表示前 i i i个括号,下斜线在 j j j,上斜线在 k k k的方案数,然后枚举当前是左括号还是右括号进行 DP \text{DP} DP即可。
复杂度 O ( n 3 + T ) O(n^3+T) O(n3+T)

【参考代码】

#include<bits/stdc++.h>
using namespace std;

const int N=1e6+10,M=305,mod=1e9+7;

namespace S1
{
	int n,pt,fg,res[N];
	char s[N];
	int check()
	{
		int d=0,u=0;
		for(int i=1;i<=n;++i) 
			if(s[i]=='(') d+=1,u+=2;
			else {d-=2,u-=1;d=max(d,0);if(u<0)return -1;}
		if(d>0) return -1;
		return u;
	}
	void solution()
	{
		int T;scanf("%d",&T);
		while(T--)
		{
			memset(res,0,(n+2)<<2);
			scanf("%s",s+1);n=strlen(s+1);
			pt=check();
			if(!~pt) {puts("impossible");continue;}

			fg=-1;
			if(pt)
			{
				for(int i=n;i;--i)
				{
					if(s[i]=='(') res[i]=fg,fg*=-1;
					else res[i]=2;
					--pt; if(!pt) break;
				}
			}
			fg=-1;
			for(int i=1;i<=n;++i) if(s[i]==')' && res[i]!=2) res[i]=fg,fg*=-1;
			for(int i=1;i<=n;++i)
			{
				if(res[i]==-1) putchar('R');
				else if(res[i]==1) putchar('B');
				else putchar('G');
			}
			puts("");
		}
	}
}

namespace S2
{
	int f[2][M<<1][M],res[M];// up down
	void up(int &x,int y){x+=y;if(x>=mod)x-=mod;}
	void solution()
	{
		f[0][0][0]=1;
		for(int i=1;i<=300;++i)
		{
			int now=i&1,las=now^1;
			memset(f[now],0,sizeof(f[now]));
			for(int j=0;j<=i*2;++j) for(int k=0;k<=i;++k)
			{
				if(k && j>=2) up(f[now][j][k],f[las][j-2][k-1]);
				up(f[now][j][k],f[las][j+1][k+2]);
				if(!k) up(f[now][j][k],f[las][j+1][k+1]),up(f[now][j][k],f[las][j+1][k]);
			}
			for(int j=0;j<=i;++j) up(res[i],f[now][j][0]);
		}
		int T;scanf("%d",&T);
		while(T--){int x;scanf("%d",&x);printf("%d\n",res[x]);}
	}
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("LOJ2713.in","r",stdin);
	freopen("LOJ2713.out","w",stdout);
#endif
	int T;scanf("%d",&T);
	if(T&1) S1::solution();
	else S2::solution();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值