2018.10.25 bzoj4350: 括号序列再战猪猪侠(区间dp)

时间:2023-03-08 20:53:06

传送门

区间dp好题。


首先我们并不用把右括号拿进来一起dpdpdp,而是直接用左括号来dpdpdp。

然后定义状态fi,jf_{i,j}fi,j​表示区间[l,r][l,r][l,r]的合法方案数。

如果没有限制直接分三种情况讨论就行了。

  1. 形如(AB)(AB)(AB)
  2. 形如()AB()AB()AB
  3. 形如(A)B(A)B(A)B

但是现在有了限制。

因此我们枚举决策的时候判当前转移是否合法。

如何判断呢?

我们建立一个数组statstatstat。

stat[a][b]!=0stat[a][b]!=0stat[a][b]!=0表示aaa对应的右括号被要求放在bbb对应的右括号前面。

stat[a][b]=0stat[a][b]=0stat[a][b]=0表示aaa对应的右括号可以不放在bbb对应的右括号前面。

这样我们就可以判断两个位置的关系啦。

但是我们还需要判断两段区间的位置关系是否合法。

因此我们对statstatstat维护一个前缀和数组sumsumsum。

sum(a,b),(c,d)=0sum_{(a,b),(c,d)}=0sum(a,b),(c,d)​=0表示aaa$c$中的数不与$b$ddd中的数冲突。

然后转移就很方便了。

代码:

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int ans=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
	return ans;
}
const int N=305,mod=998244353;
int T,n,m,f[N][N],sum[N][N];
inline int calc(int x1,int y1,int x2,int y2){return sum[x2][y2]+sum[x1-1][y1-1]-sum[x1-1][y2]-sum[x2][y1-1];}
inline int solve(){
	for(int i=1;i<=n;++i)if(calc(i,i,i,i))return 0;
	for(int i=n;i;--i){
		f[i][i]=1;
		for(int j=i+1;j<=n;++j){
			if(!calc(i,i+1,i,j))(f[i][j]+=f[i+1][j])%=mod;
			if(!calc(i+1,i,j,i))(f[i][j]+=f[i+1][j])%=mod;
			for(int m=i+1;m<j;++m)if(!calc(m+1,i+1,j,m)&&!calc(i,i+1,i,m)&&!calc(m+1,i,j,i))(f[i][j]+=1ll*f[i+1][m]*f[m+1][j]%mod)%=mod;
		}
	}
	return f[1][n];
}
int main(){
	T=read();
	while(T--){
		n=read(),m=read();
		for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)sum[i][j]=f[i][j]=0;
		for(int i=1,a,b;i<=m;++i)a=read(),b=read(),sum[a][b]=1;
		for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)sum[i][j]=sum[i][j]+sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
		printf("%d\n",solve());
	}
	return 0;
}