[AGC027E]ABBreviate

Description

AGC027E
给定一个仅由\(AB\)构成的字符串\(S\),给定两个操作,把\(AA\)换成\(B\),和把\(BB\)换成\(A\),问由这个字符串和任意次操作可以得到几种字符串。

Solution

AGC好难啊(摔

首先,如果不能操作,答案是\(0\)

我们令\(A=1,B=2\),这样就会有一个美妙的性质:所有字符的和在操作的时候在模\(3\)意义下不变,我们记这个和为\(p(S)\)。然后我们可以得到一个结论:一个字符串\(S\)可以变成字符\(c\),当且仅当:\(S=c\)\(p(S)=p(c)\)\(S\)可以操作。这个条件的必要性很好证明,而充分性证明也比较简单:当\(S\)中的连续字符长度为\(2\)时,可以合并这两个字符,并且字符串长度减小;而当\(S\)中的连续字符长度大于\(2\)时,可以合并与不同字符相邻的那一端的字符(如果只有一种字符的话,自己证吧),字符串长度也减少;这样,字符串长度不断减少,一定可以达到\(1\),这时的\(S'=c\)

这样问题就转化为:求出这样的\(T\)的数量:将\(S\)划分为\(|T|\)段,第\(i\)段的\(p\)值与\(T\)中的第\(i\)个字符的\(p\)值相等。我们可以贪心的构造这样的\(T\):让每一段的长度尽可能的小,如果分出\(|T|\)段后,剩余部分的\(p\)值为\(0\),那就是一个合法的\(T\)

这样,就只需要一个简单的DP就行了我们递推的来做,\(f_i\)表示前\(i\)个字符看作一段的方案数,如果\(p(S_{i+1,n})=0\),那么\(i\)之后的位置可以看成是剩余的位置\(f_i\)的初值为\(1\),否则为\(0\)。然后,我们可以在\(i\)之后加一段\(A\)或是\(B\),这样就要记录各个\(p\)值最后一次出现的位置,累加即可。具体实现看代码。

Code

#include 

const int N = 1e5 + 10;
const int MOD = 1e9 + 7;

char s[N];
int a[N], n, f[N], nxt[3];

int main() {
    scanf("%s", s);
    for (; s[n]; ++n) a[n+1] = (a[n] + s[n] - 'a' + 1) % 3; // 求前缀和 
    // 判断是否可以操作 
    int flag = 0;  
    for (int i = 1; i < n; ++i) {
        if (s[i] == s[i-1]) flag = 1;
    }
    if (!flag) {
        puts("1");
        return 0;
    }
    // 递推 
    f[n] = 1;
    for (int i = 0; i < 3; ++i) nxt[i] = i == a[n] ? n : n + 1;
    for (int i = n-1; i; --i) {
        f[i] = a[i] == a[n]; // 后面可不可以不分段 
        for (int c = 1; c <= 2; ++c) {
            (f[i] += f[nxt[(a[i]+c)%3]]) %= MOD; // 后面加一段A或B 
        }
        nxt[a[i]] = i; // 记录该p值最后一次出现的位置 
    }
    printf("%d\n", (f[nxt[1]] + f[nxt[2]]) % MOD);
    return 0;
}

转载于:https://www.cnblogs.com/wyxwyx/p/agc027e.html

你可能感兴趣的:([AGC027E]ABBreviate)