luogu-P12472题解
前言
补发一篇一个月前写的题解。
题解
神仙题,弱化版是 AGC055D。
首先观察数据范围,发现 \(n\) 很小于是考虑高维 dp 或者 状压 dp。在 dp 前我们需要去寻找一些性质,考虑怎么样的序列是合法的?我们尝试用一些必要条件去刻画它,这里引用某选手说过的一句话:必要条件多了就可以变成充要条件。
我们可以观察三个不同的子序列:ABC,BCA,CAB。因为这些子序列都有相同的字母但是这些字母的位置不同,所以我们尝试去研究不同字母之间的位置关系。能够发现:只有在 ABC 中,A 才会出现在 C 的前面,否则其在 C 的后面。对于其他两对字母也存在类似的关系。设 \(pre_a,pre_b,pre_c\) 分别表示前缀 A,B,C 的个数,我们可以发现对于任意前缀都有:\(pre_a-pre_c\le cnt_{ABC}\)。其他字母同理。
由此我们得到一个神秘的条件:
现在如果我能够通过上述条件构造出合法的解,那么这个条件就为充要条件,于是我们尝试构造一个合法的序列。首先对于一个序列,如果其合法,那么我们将下标平移不会影响其合法性。比如我们把最开始的字母移到最后,可以发现:这个字母所在的子序列进行了一次轮换,其余子序列不变。所以新的序列一定是合法的。于是我们考虑将序列连成一个环,下面给出构造方案:对于环上第 \(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;
}

浙公网安备 33010602011771号