luogu-P12472题解

前言

补发一篇一个月前写的题解。

题解

神仙题,弱化版是 AGC055D

首先观察数据范围,发现 \(n\) 很小于是考虑高维 dp 或者 状压 dp。在 dp 前我们需要去寻找一些性质,考虑怎么样的序列是合法的?我们尝试用一些必要条件去刻画它,这里引用某选手说过的一句话:必要条件多了就可以变成充要条件。

我们可以观察三个不同的子序列:ABCBCACAB。因为这些子序列都有相同的字母但是这些字母的位置不同,所以我们尝试去研究不同字母之间的位置关系。能够发现:只有在 ABC 中,A 才会出现在 C 的前面,否则其在 C 的后面。对于其他两对字母也存在类似的关系。设 \(pre_a,pre_b,pre_c\) 分别表示前缀 A,B,C 的个数,我们可以发现对于任意前缀都有:\(pre_a-pre_c\le cnt_{ABC}\)。其他字母同理。

由此我们得到一个神秘的条件:

\[\max(pre_a-pre_c)+\max(pre_b-pre_a)+\max(pre_c-pre_b)\le n \]

现在如果我能够通过上述条件构造出合法的解,那么这个条件就为充要条件,于是我们尝试构造一个合法的序列。首先对于一个序列,如果其合法,那么我们将下标平移不会影响其合法性。比如我们把最开始的字母移到最后,可以发现:这个字母所在的子序列进行了一次轮换,其余子序列不变。所以新的序列一定是合法的。于是我们考虑将序列连成一个环,下面给出构造方案:对于环上第 \(i\)A,对应环上第 \(i+cnt_{BCA}\)B 和环上第 \(i+cnt_{BCA}+cnt_{CAB}\)C

于是现在我们需要证明的是:对于任意的 \(i\),环上第 \(i+cnt_{BCA}+cnt_{CAB}\)C 不在环上第 \(i\)A 和环上第 \(i+cnt_{BCA}\)B 之间。

证明是容易的。我们考虑从第 \(i\)A 处断环成链,于是我们需要证明的是原第 \(i+cnt_{BCA}+cnt_{CAB}\)C 的位置小等于 \(3n\)。考虑到原第 \(i+cnt_{BCA}+cnt_{CAB}\)C 在新的序列中处于第 \(1+cnt_{BCA}+cnt_{CAB}\)C,考虑最坏的情况是所有 C 都在 A,B 后面,因为有 \(cnt_{ABC}+cnt_{BCA}+cnt_{CAB}=n\) 并且因为新的序列开头是 A 所以 \(cnt_{ABC}>0\),进一步得到 \(1+cnt_{BCA}+cnt_{CAB}\le n\),于是得证。

对于弱化版,当 \(n\le15\) 时,我们可以直接 \(\mathcal O(n^6)\) dp 直接记录所有信息即可。具体的,设 \(f_{i,j,k,a,b,c}\) 维护前缀 A,B,C 的数量以及 \(cnt_{ABC},cnt_{BCA},cnt_{CAB}\) 然后 \(\mathcal O(1)\) 转移。

考虑 \(n\le 60\) 时,我们尝试干掉一维状态。因为有 \(cnt_{ABC}+cnt_{BCA}+cnt_{CAB}=n\) 所以我们可以枚举 \(cnt_{ABC}\)\(cnt_{BCA}\) 从而确定 \(cnt_{CAB}\),但是考虑到直接做等于 \(cnt\) 的限制不太好 dp,于是我们改成做 \(\le cnt\) 的限制最后差分一下即可,时间复杂度 \(\mathcal O(n^5)\)

代码

inline unsigned calc(int a, int b, int c){
    if(min(a, b) < 0)return 0; memset(f, 0, sizeof f); f[0][0][0] = 1;
    for(int i = 0; i <= n; ++i)for(int j = 0; j <= min(n, i + b); ++j)
        for(int k = max(0, i - a); k <= min(n, j + c); ++k)if(i + j + k != 3 * n){
            int pos = i + j + k;
            if(s[pos] == '?' or s[pos] == 'A')f[i + 1][j][k] += f[i][j][k];
            if(s[pos] == '?' or s[pos] == 'B')f[i][j + 1][k] += f[i][j][k];
            if(s[pos] == '?' or s[pos] == 'C')f[i][j][k + 1] += f[i][j][k];
        }
    return f[n][n][n];
}

void sol(){
    cin >> n >> ss[0] >> ss[1] >> s; ans = 0;
    if(n != 60)return cout << - 1 << endl, void();
    for(int a = 0; a <= n; ++a)for(int b = 0; a + b <= n; ++b){
        int x = a, y = b; while(x <= n and ss[0][x] == '0')++x;
        while(y <= n and ss[1][y] == '0')++y; int c = n - x - y;
        if(c < 0)continue; ans += calc(a, b, c) - calc(a - 1, b, c) - calc(a, b - 1, c) + calc(a - 1, b - 1, c);
    }cout << ans  << endl;
}

signed main(){
    ios :: sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
    int T, id; cin >> T >> id; while(T--)sol(); return 0;
}
posted @ 2025-08-18 21:01  Lyrella  阅读(36)  评论(0)    收藏  举报