【bzoj4350】括号序列再战猪猪侠 区间DP

Description

括号序列与猪猪侠又大战了起来。
众所周知,括号序列是一个只有(和)组成的序列,我们称一个括号
序列S合法,当且仅当:
1.( )是一个合法的括号序列。
2.若A是合法的括号序列,则(A)是合法的括号序列。
3.若A,B是合法的括号序列,则AB是合法的括号序列。
我们考虑match[i]表示从左往右数第i个左括号所对应的是第几个右
括号,现在他得到了一个长度为2n的括号序列,给了你m个信息,第i
个信息形如ai,bi,表示match[ai]

Input

第一行一个正整数T,T< = 5,表示数据组数。
对于每组数据,第一行一个n,m,n表示有几个左括号,m表示信息数。
接下来m行,每行两个数ai,bi,1< = ai,bi< = n。

Output

对于每组数据,输出一个数表示答案。

Sample Input

5

1 0

5 0

3 2

1 2

2 3

3 2

2 1

2 3

3 3

1 2

2 3

3 1

Sample Output

1

42

1

2

0

HINT

对于前两个点,是卡特兰数的情况。

对于第三个点,合法的情况只可能是 ()()()。

对于第四个点,合法情况可能是 (()()) 或者 (())()

对于第五个点,由于拓扑关系形成了环,显然无解。

对于 100% 的数据,保证 n < = 300

Source


ydc给我们的考试题…T1…………

ydc:这道题是本场考试最水的

具体做法是区间dp,状态转移方程也挺好写,三种情况:()AB,(AB),(A)B。
重点在如何处理【i的右括号必须在j的右括号左边】这个约束。

二维前缀和。sum[i][j]为1表示i必须在j之前。考虑一个矩形所代表的含义:

先考虑边长为1的矩形。矩形((a,b),(a,c))的权值若是1,则表示a必须在b~c中某个的前面。若为0,则表示a没必要在b~c的任意一个数左边,也就是a的右括号可以放c后面或者b前面。

我们发现矩形权值为0的性质可以利用。考虑((a,b),(c,d))的含义:若权值为0,则表示a~c中任意一个元素对于b~d中任意一个元素都没有条件约束。这个性质可以快速查询一个括号序列是否可以在另一个括号序列之前。

状态dp[i][j]表示第i个左括号到第j个左括号组成的括号序列方案数,然后根据二维前缀和查询DP就行了…

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

typedef long long LL;
const int SZ = 500;
const int mod = 998244353;

int dp[SZ][SZ];
int sum[SZ][SZ];

int getsum(int a,int b,int c,int d)
{
    return sum[c][d] - sum[c][b - 1] - sum[a - 1][d] + sum[a - 1][b - 1];
}

int ask(int n)
{
    for(int i = 1;i <= n;i ++)
        if(getsum(i,i,i,i)) return 0;
    for(int i = n;i >= 1;i --)
    {
        dp[i][i] = 1;
        for(int j = i + 1;j <= n;j ++) 
        {
            if(!getsum(i,i + 1,i,j)) //(AB)
                dp[i][j] = (dp[i][j] + dp[i + 1][j]) % mod;
            if(!getsum(i + 1,i,j,i)) //()AB
                dp[i][j] = (dp[i][j] + dp[i + 1][j]) % mod;
            for(int k = i + 1;k < j;k ++) //(A)B
                if(!getsum(i,i + 1,i,k) && !getsum(k + 1,i,j,i) && !getsum(k + 1,i + 1,j,k))
                    dp[i][j] = (dp[i][j] + (LL)dp[i + 1][k] * dp[k + 1][j] % mod) % mod;
        }       
    }
    return dp[1][n];
}

void init()
{
    memset(dp,0,sizeof(dp));
    memset(sum,0,sizeof(sum));
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T --)
    {
        init();
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i = 1;i <= m;i ++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            sum[x][y] = 1;
        }
        for(int i = 1;i <= n;i ++)
            for(int j = 1;j <= n;j ++)
                sum[i][j] += sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];
        printf("%d\n",ask(n));      
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值