【题目】
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,n≤300
【解题思路】
如何判断一个字符串是不是合法的?我们先将左括号看作
+
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 < 0 d_1<0 d1<0,则将 d 1 = 0 d_1=0 d1=0,若 d 2 < 0 d_2<0 d2<0,则没有合法方案。若最终 d 1 > 0 d_1>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;
}