【Atcoder - AGC026E】Synchronized Subsequence

@Synchronized Subsequence@


@题目描述 - English@

Time limit : 2sec / Memory limit : 1024MB

Score : 1600 points

Problem Statement
You are given a string S of length 2N, containing N occurrences of a and N occurrences of b.

You will choose some of the characters in S. Here, for each i=1,2,…,N, it is not allowed to choose exactly one of the following two: the i-th occurrence of a and the i-th occurrence of b. (That is, you can only choose both or neither.) Then, you will concatenate the chosen characters (without changing the order).

Find the lexicographically largest string that can be obtained in this way.

Constraints
1≤N≤3000
S is a string of length 2N containing N occurrences of a and N occurrences of b.

Input
Input is given from Standard Input in the following format:
N
S

Output
Print the lexicographically largest string that satisfies the condition.

Sample Input 1
3
aababb
Sample Output 1
abab
【A subsequence of T obtained from taking the first, third, fourth and sixth characters in S, satisfies the condition.】

Sample Input 2
3
bbabaa
Sample Output 2
bbabaa
【You can choose all the characters.】

Sample Input 3
6
bbbaabbabaaa
Sample Output 3
bbbabaaa

Sample Input 4
9
abbbaababaababbaba
Sample Output 4
bbaababababa

@题目大意@

一个长度为 2 N 的字符串S包含着 N 个a与 N 个b。选择其中的一些字母,要求第i个a与第i个b要么都选要么都不选。不改变顺序地连接这些字母组成一个新串。
给定N与S,找到按照这个方法得到的最大字典序串。

@分析@

第一对(a,b)具有特殊性,因为第一对(a,b)之间全是相同的字母,所以我们先考虑它。
假如不保留第一对,则我们就要在第2~N对之间寻找答案。
假如保留第一对,我们分为两类讨论:
1)第一个a在第一个b前,即 a a a a a b + s 。贪心地考虑,不难发现第一对(a,b)之间的a不保留要比保留要优秀。假设第一个b前面的a是第k个,则总的最优串 = 第一对(a,b) + 第k+1~N个对(a,b)所得到的最优串
2)第一个b在第一个a前,即 b b b b b a + s 。依然贪心地考虑,我们发现第一对(a,b)之间的b保留要比不保留要优秀。之后的结论不同于上一类,因为我们保留了会对后面结果产生的(a,b),结论不再是“总的最优串 = 第1~k对(a,b) + 第k+1~N个对(a,b)所得到的最优串”【考场上就是卡在这个点上了】具体是什么我们待会儿细致地说。
初步分析可以大致得到这样一个结论:该问题具有最优子结构的性质!!!
这极有可能是一道dp题!!!

@详细的解析@

定义状态 d p [ i ] [ j ] 为一个0-1状态,表示使用第i~N对串得到的最优串中,1表示第j对保留,0表示第j对不保留。
如果不选第i对,则:

d p [ i ] [ j ] = d p [ i + 1 ] [ j ] ( 1 <= j <= N )

如果选第i对:
1)第i个a在第i个b前,根据上面的分析,有:
d p [ i ] [ j ] = 0 ( 1 <= j <= i 1

d p [ i ] [ i ] = 1

d p [ i ] [ j ] = 0 i + 1 <= j <= k

d p [ i ] [ j ] = d p [ k + 1 ] [ j ] k + 1 <= j <= N

k为第i个a~第i个b之间标号最大的a的标号
2)第i个b在第i个a前,咱们不妨看这样一个串:
b 1 b 2 b 3 a 1 a 2 b 4 b 5 a 3 a 4 a 5 a 6 a 7 b 6 a 8 b 7 b 8

【有些长……保持大脑清晰!】
现在假设我们选择了 ( b 1 , a 1 ) ,显然 b 2 , b 3 是需要被保留的,不然字典序会变小。因此 a 2 , a 3 被顺带保留下来了。接下来,我们发现因为 a 2 , a 3 被保留下来了,因此 b 4 , b 5 保留下来字典序会更大。所以我们得到 b 4 , b 5 也必须保留。 a 4 , a 5 保留下来对后面的 b 6 8 没有影响,所以这又变成了一个dp的子问题。
【建议上例结合着代码一起看】
【只求意会!因而无法言传嗯嗯】
这个状况下没有通用的转移式,所以我就不写了(`・ω・´)

@代码部分@

代码中还有一些细节,建议实在不会的话,可以参考一下。
最好把代码结合上面的例子一起看

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN = 3000;
char S[2*MAXN + 5];
int a[MAXN + 5], b[MAXN + 5], c[2*MAXN + 5];
int dp[MAXN + 5][MAXN + 5];
int N, A, B;
string fun(int num[]) {
    string ret = "";
    for(int i=1;i<=2*N;i++)
        if( num[c[i]] ) ret.insert(ret.end(), S[i]);
    return ret;
}
int main() {
    scanf("%d", &N);
    scanf("%s", S+1);
    for(int i=1;i<=2*N;i++) {
        if( S[i] == 'a' ) {
            a[++A] = i;
            c[i] = A;
        }
        else {
            b[++B] = i;
            c[i] = B;
        }
    }
    dp[N][N] = 1;
    for(int i=N-1;i>=1;i--) {
        dp[i][i] = 1;
        if( a[i] < b[i] ) {
            int Max = 0;
            for(int j=a[i];j<=b[i];j++) {
                if( c[j] < i ) continue;
                Max = max(Max, c[j]);
            }
            for(int j=b[i]+1;j<=2*N;j++)
                if( c[j] > Max )
                    dp[i][c[j]] = dp[Max+1][c[j]];
        }
        else {
            int tot = 0, Max = 0;
            for(int j=b[i];j<=a[i];j++) {
                if( c[j] < i ) continue;
                if( c[j] != i ) tot++;
                Max = max(Max, c[j]);dp[i][c[j]] = 1;
            }
            for(int j=a[i]+1;j<=2*N;j++) {
                if( S[j] == 'b' ) {
                    if( tot == 0 ) {
                        dp[i][c[j]] = dp[Max+1][c[j]];
                    }
                    else {
                        dp[i][c[j]] = 1;
                        tot++;
                    }
                }
                else {
                    if( tot == 0 ) {
                        dp[i][c[j]] = dp[Max+1][c[j]];
                    }
                    else {
                        dp[i][c[j]] = 1;
                        Max = max(Max, c[j]);
                        tot--;
                    }
                }
            }
        }
        if( fun(dp[i]) < fun(dp[i+1]) )
            memcpy(dp[i], dp[i+1], sizeof dp[i+1]);
    }
    cout << fun(dp[1]);
}

@END@

就是这样,新的一天里,也请多多关照哦(ノω<。)ノ))☆.。~

猜你喜欢

转载自blog.csdn.net/tiw_air_op1721/article/details/81056389