SDUT 2022 Winter Individual Contest - A(D)

20 篇文章 0 订阅

D - Brackets

链接: link

题意:

给定一个括号序列,现在允许最多反转一段区间,使得 ( ( (变成 ) ) ) ) ) )变成 ( ( (,问这段括号序列在最多可反转一次情况下,能否是合法括号序列

思路:

两重循环枚举需要反转的区间,然后判断反转后,当前这种序列是否合法,针对判断序列合法,有两种判断方法,一种是记忆化搜索,另一种就是区间和+最大最小值判断法
两种方法都需要预处理, ( ( (视为 1 1 1 ) ) )视为 − 1 -1 1
记忆化搜索的话,定义 f ( p o s , s u m , s t a t e ) f(pos,sum,state) f(pos,sum,state)为判断位置到了 p o s pos pos, 1 − p o s − 1 1-pos-1 1pos1的总和,state为反转的三种状态
0 0 0代表直到当前位置为止,一个都没括号都没反转
1 1 1代表当前这个位置反转了
2 2 2代表前面出现过反转了,后面不会再出现反转
这就相当于枚举反转区间
001122 001122 001122,这样的序列就代表 [ 1 , 2 ] [1,2] [1,2]没反转, [ 3 , 4 ] [3,4] [3,4]反转, [ 5 , 6 ] [5,6] [5,6]没反转
这样的话,当遍历完整个序列时,判断序列是否合法,只需要看区间的总和是否为 1 1 1,且区间最小值是否 ≥ 0 ≥0 0(在遍历过程中,通过总和是否 < 0 <0 <0来判断,效果一样)

#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 5555;
char s[N];
int a[N];
int len;
int dp[N][N][3];
int vis[N][N][3];
bool f(int pos, int sum, int state) {
    if (sum < 0) return 0;
    if (state > 2) return 0;
    if (pos == len + 1) {
        if (sum == 0) return 1;
        return 0;
    }
    if (vis[pos][sum][state]) return dp[pos][sum][state];

    bool now = f(pos, sum, state + 1);
    if (state == 0)
        now |= f(pos + 1, sum + a[pos], state);
    else if (state == 1)
        now |= f(pos + 1, sum - a[pos], state);
    else
        now |= f(pos + 1, sum + a[pos], state);
    vis[pos][sum][state] = 1;

    return dp[pos][sum][state] = now;
}
int main() {
    cin >> s + 1;
    len = strlen(s + 1);
    for (int i = 1; i <= len; i++) {
        if (s[i] == '(')
            a[i] = 1;
        else
            a[i] = -1;
    }
    if (f(1, 0, 0))
        puts("possible");
    else
        puts("impossible");
}

第二种方法区间和+最大最小值
先预处理出来区间和,区间最大最小值
然后再枚举反转的区间
假设枚举的区间为 [ l , r ] [l,r] [l,r]
那么当前区间想要合法的条件是
s u m ( 1 , l − 1 ) − s u m ( l , r ) + s u m ( r , n ) = 0 sum(1,l-1)-sum(l,r)+sum(r,n)=0 sum(1,l1)sum(l,r)+sum(r,n)=0首先要保证整段区间的左括号右括号相同,因为是反转,所以带负号
m i n ( 1 , l − 1 ) ≥ 0 min(1,l-1)≥0 min(1,l1)0 a n d and and s u m ( 1 , l − 1 ) − m a x ( l , r ) > = 0 sum(1,l-1)-max(l,r)>=0 sum(1,l1)max(l,r)>=0 a n d and and s u m ( 1 , l − 1 ) − s u m ( l , r ) + m i n ( r + 1 , n ) > = 0 sum(1,l-1)-sum(l,r)+min(r+1,n)>=0 sum(1,l1)sum(l,r)+min(r+1,n)>=0
m i n v ( 1 , l − 1 ) < 0 minv(1,l-1)<0 minv(1,l1)<0就说明出现了右括号过多的情况,或者左括号还没出现,右括号就出现了,无法匹配的情况,此时无论怎么反转右边的区间都不能合法
s u m ( 1 , l − 1 ) − m a x ( l , r ) < 0 sum(1,l-1)-max(l,r)<0 sum(1,l1)max(l,r)<0,说明在反转后, [ l , r ] [l,r] [l,r]过多的左括号无法与弥补左区间的右括号,所以说明反转后 [ 1 , r ] [1,r] [1,r]这段区间右括号剩下了,无法匹配的情况
s u m ( 1 , l − 1 ) − s u m ( l , r ) + m i n ( r + 1 , n ) < 0 sum(1,l-1)-sum(l,r)+min(r+1,n)<0 sum(1,l1)sum(l,r)+min(r+1,n)<0,说明在反转后 [ 1 , n ] [1,n] [1,n]这段区间出现了,右括号剩下,无法匹配的现象
只要都满足,说明这段区间在修改后,是合法的

#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 5555;
char s[N];
int sum[N][N];
int maxv[N][N], minv[N][N];

int main() {
    cin >> s + 1;
    int len = strlen(s + 1);
    for (int i = 1; i <= len; i++) {
        int all = 0;
        int Max = 0, Min = 0;
        for (int j = i; j <= len; j++) {
            if (s[j] == '(')
                all++;
            else
                all--;
            sum[i][j] = all;
            Max = max(Max, all);
            Min = min(Min, all);
            maxv[i][j] = Max;
            minv[i][j] = Min;
        }
    }

    if (sum[1][len] == 0 && minv[1][len] == 0) {
        puts("possible");
        return 0;
    } else {
        bool flag = 0;
        for (int i = 1; i <= len; i++) {
            for (int j = i; j <= len; j++) {
                if (sum[1][i - 1] - sum[i][j] + sum[j + 1][len] != 0) {
                    continue;
                }
                if (minv[1][i - 1] < 0 || sum[1][i - 1] - maxv[i][j] < 0 ||
                    sum[1][i - 1] - sum[i][j] + minv[j + 1][len] < 0)
                    continue;
                flag = 1;
                break;
            }
            if (flag) break;
        }
        if (flag)
            puts("possible");
        else
            puts("impossible");
    }
}

To be continued
如果你有任何建议或者批评和补充,请留言指出,不胜感激

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值