CF 506E Mr. Kitayuta's Gift

这题真是神了。
没有代码居然题解源码就有 6.4k 了。

\(m = |s|\)\(n\) 为最后串的总长度(\(n = n' + m\)\(n'\) 为题目给出的那个 \(n\))。

首先考虑这个回文串的限制,那么就要求第 \(i\) 个字符和第 \(n - i + 1\) 个字符相同。
那么此时发现这个限制相当于是对于头尾(或者说前后)的限制,于是考虑就以此作为一个阶段 dp。
具体来说,维护一个 \(f_{i, *}\) 代表已经确定了 \(1\sim i\)(也就确定了 \(n - i + 1\sim n\)),同时还会满足条件 \(*\)(暂时不清楚是什么)的方案数。

此时再回到题目给的另一个限制,\(s\) 必须为最终串的一个子序列。
考虑怎么判定一个串是否是另一个串的子序列,一个办法就是贪心的选最靠前的,能选出就可以。
但是考虑到已有的限制,让新的限制尽可能贴合已有的限制肯定更好处理,此时再结合贪心也可以倒着贪,所以可以把判定改成:两头都分别贪心,前面的尽可能选靠前,后面的尽可能选靠后。

于是就可以补足状态:\(f_{i, l, r}\) 代表确定了 \(1\sim i\)\(n - i + 1\sim n\)),且当前还有原串的 \([l, r]\) 还没有匹配上。
接下来转移时发现还有一点漏洞:对于答案并不能很好地表示。
于是再引入 \(g_i\) 代表确定了 \(1\sim i\),且已经全部匹配上的方案数。

因为 \(n\) 为奇数时发现最后一步并不是很类似其他步,所以此时先假设 \(n\) 为偶数考虑转移:

  • \(s_l = s_r\),那么此时如果选择了 \(s_l\),就能贪心的匹配上 \(l, r\);否则匹配不上。
    注意一下到答案的转移,有 \(f_{i + 1, l, r}\leftarrow f_{i, l, r}\times 25\),若 \(r\le l + 1\)\(g_{i + 1}\leftarrow f_{i, l, r}\),否则 \(f_{i + 1, l + 1, r - 1}\leftarrow f_{i, l, r}\)
  • \(s_l\not = s_r\),那么选择了 \(s_l\)\(s_r\) 就可以使边界动 \(1\),否则不动。
    于是有转移,\(f_{i + 1, l + 1, r}, f_{i, l, r - 1}\leftarrow f_{i, l, r}, f_{i + 1, l, r}\leftarrow f_{i, l, r}\times 26\)
  • 已经匹配完了(\(g_i\))。
    那么此时只需要对称的两个位置选的是一样的就可以了:\(g_{i + 1}\leftarrow g_i\times 26\)

因为 \(n\) 很大,对于 \(i\in [0, \frac{n}{2} - 1]\) 都要转移肯定是不现实的。
所以有一个想法就是对于 \(i\)\(f_{i, l, r}, g_i\) 放到一个矩阵中,做矩阵快速幂得到答案。
但是发现此时这个矩阵的规模达到了 \(\mathcal{O}(m^3)\),时间复杂度就到了 \(\mathcal{O}(m^6\log n)\)

于是从中知道的是,要尝试去减小矩阵的规模

首先可以把这个矩阵刻画成一个带权有向图,点存在 \(g, f_{*, l, r}\)
而这个图的边其实就是转移方程的这些式子,起点和终点分别为转移的值和转移到的值,边权为转移带的常数。

那么此时在转移图上考虑答案的意义,就是所有从起点开始走 \(\frac{n}{2}\) 步走到终点的路径的边权乘积的总和
刚刚用矩阵乘法的做法实质上是在整体的考虑这个总和,于是尝试另一种思路:对每一条路径分别考虑再求和

\(s_l \not = s_r\) 的点为 \(\operatorname{A}\) 类点,记 \(s_l = s_r\) 的点为 \(\operatorname{B}\) 类点,\(g\) 记做 \(\operatorname{C}\) 类点。

但是对于路径的长度还是达到了 \(\frac{n}{2}\),依然有很多种,于是尝试缩减路径的种类(注意是种类而不是长度)
首先一个观察是,路径中其实很多都在走自环。所以尝试只把路径表示成经过的不超过 \(m + 1\) 个点,但是此时就忽略了自环的影响,但是这个依然是可以对每种路径做矩阵快速幂的(\(f_{*, i}\) 表示当前在 \(i\) 点,接下来就是走到 \(i\)\(i + 1\))。
此时长度被缩减到了 \(\le m + 1\),但是种类依然很多。
继续考虑缩减数量,首先末尾一定是 \(\operatorname{C}\) 类点所以可以忽略掉。
其次就是发现,如果两条路径 \(\operatorname{A}\) 类和 \(\operatorname{B}\) 类点数量相同那么也是等价的,因为此时可以考虑分别给 \(\operatorname{A}\) 类和 \(\operatorname{B}\) 类点建一个双射出来,每个路径都能对应上。

于是此时会发现因为 \(\operatorname{A}\) 类点和 \(\operatorname{B}\) 类点的数量总和是 \(\le m\) 的,且会发现 \(\operatorname{B}\) 类点 \(cnt_{\operatorname{B}}\) 能被 \(\operatorname{A}\) 类点 \(cnt_{\operatorname{A}}\) 唯一表示:\(c_{\operatorname{B}} = \lceil\frac{m - \operatorname{c_A}}{2}\rceil\)

于是这说明此时的路径的种类数已经不超过 \(m\) 了(倒数第二个必须是 \(\operatorname{B}\) 类点),此时用 \(c_{\operatorname{A}}\) 来表示其对应的一类路径。
于是就可以尝试对会其方案数计数了,会发现其受两个因素的影响:在该路径上走的方案数和该路经在原转移图上的方案数,这两个值乘积就是其方案数。
对于第一个方案数,可以和上文说的一样,设 \(f_{i, j}\) 为第 \(i\) 轮走到了 \(j\) 号点的方案数,接下来有两种选择:\(j\to j\)\(j\to j + 1\);对于第二个方案数,可以直接在这个转移图上 dp(此时对于每种路径考虑就不如整体考虑了):设 \(f_{l, r, c_{\operatorname{A}}}\) 为当前转移图上在 \([l, r]\) 点,且对应的 \(c_{\operatorname{A}}\) 的这个值也确定的方案数。
第一个方案数求解时间复杂度为 \(\mathcal{O}(m^4\log n)\),第二个方案数求解时间复杂度为 \(\mathcal{O}(m^3)\)

发现此时第一个方案数求解的复杂度还是不理想,继续对这一部分优化。
首先要尝试找到这个做法的瓶颈之处:一共有 \(m\) 种路径,而每一种路径都需要 \(\mathcal{O}(n^3\log n)\) 的复杂度来做矩阵快速幂。
于是这启发去丢掉第一个 \(m\),不去单独求解每一种路径的值

所以尝试去建一个新的转移图出来,使得这个转移图的点数在可接受范围内且可以转移出所有的路径。
那么就需要把所有 \((c_{\operatorname{A}}, c_{\operatorname{B}})\) 表示出来,那么不妨先钦定每一条 \((c_{\operatorname{A}}, c_{\operatorname{B}})\) 对应的路径都是先经过 \(c_{\operatorname{A}}\)\(\operatorname{A}\) 点再经过 \(\operatorname{B}\)\(c_{\operatorname{B}}\) 点,这样是为了让所有路径都具有普适性方便构造。
此时就需要注意到 \(c_{\operatorname{A}}\)\(c_{\operatorname{B}}\) 之间的联系了:\(c_{\operatorname{B}} = \lceil\frac{m - \operatorname{c_A}}{2}\rceil\),那么这说明着 \(c_{\operatorname{A}}\) 单调增时,\(c_{\operatorname{B}}\) 不增
这个很美妙的性质便引导去对 \(c_{\operatorname{A}}\)\(c_{\operatorname{B}}\) 分别建出转移图再合并起来。
具体来说,建立 \(p_{\operatorname{A}, i}\) 代表 \(\operatorname{A}\) 的第 \(i\) 个点,那么可以建出转移图:\(p_{\operatorname{A}, i}\stackrel{24}{\to} p_{\operatorname{A}, i}, p_{A, i - 1}\stackrel{1}{\to} p_{\operatorname{A}, i}(i\in [1, m])\)
建立 \(p_{\operatorname{B}, i}\) 代表 \(\operatorname{B}\) 的第 \(i\) 个点,那么可以建出转移图:\(p_{\operatorname{B}, i}\stackrel{25}{\to}p_{\operatorname{B}, i}, p_{\operatorname{B}, i}\stackrel{1}{\to} p_{\operatorname{B}, i - 1}(i\in [1, \lceil\frac{m}{2}\rceil]), p_{\operatorname{B}, 0}\stackrel{26}{\to}p_{\operatorname{B}, 0}\)
此时还要考虑一个问题:在前面算 \(c_{\operatorname{A}}\) 的方案数是其路径方案数乘上在原转移图中的数量,记为 \(g_{c_{A}}\)。但到了这个新转移图上,又如何处理这个 \(g\) 的贡献呢?
此时会发现因为钦定了路径是 \(p_{\operatorname{A}, 0}\to p_{\operatorname{A}, c_{\operatorname{A}}}\to p_{\operatorname{B}, c_{\operatorname{B}}}\to p_{\operatorname{B, 0}}\),又因为 \((c_{\operatorname{A}}, c_{\operatorname{B}})\) 是不重的,所以可以考虑修改边权 \(p_{\operatorname{A}, c_{\operatorname{A}}}\stackrel{g_{c_A}}{\to} p_{\operatorname{B}, c_{\operatorname{B}}}\)
于是此时所有转移都被考虑完全了!

有一些需要注意的点:
\(p_{\operatorname{A}, 0}\stackrel{0}{\to} p_{\operatorname{A}, 0}\),这其实是把 \(p_{\operatorname{A}, 0}\) 当作了一个起点,这个的必要性是会发现路径可能不会经过 \(\operatorname{A}\) 点而全是 \(\operatorname{B}\) 点,如果没有 \(p_{\operatorname{A}, 0}\) 就无法表示出这个路径。
对应的,因为人为的加入了一个起点,也人为的多加了一步,实际上在该图上走的步数应该是 \(\frac{n}{2} + 1\)
同样的,\(p_{\operatorname{B}, 0}\stackrel{26}{\to}p_{\operatorname{B}, 0}\),这里的 \(p_{\operatorname{B}, 0}\) 其实指的是最后的那个 \(\operatorname{C}\) 类点。

于是此时这个图的点数是 \(m + \lceil\frac{m}{2}\rceil + 2\) 的,做矩阵快速幂就可以做到 \(\mathcal{O}(m^3\log n)\) 的复杂度了。

接下来考虑 \(n\) 为奇数的情况,奇数的问题在上文就提到了:最后一步不同于前面的步。
而特殊之处就是在与,最后一步只有一个字符,对应的能填充的边界就只能动 \(1\)
于是需要回到一开始的转移式子,分析中途的哪些步是不能达成的了:当 \(l + 1 = r, s_l = s_r\) 时,不能通过放 \(s_l\) 转移到 \(g\) 了,而此时正为最后一步,这个方案应该不被记入方案中。

于是可以考虑容斥,减掉这最后一步的情况。
总的方案数那就是仍然认为最后一步可以直接转移,那就还是和偶数一样,只不过变成了走 \(\lceil\frac{n}{2}\rceil\) 步(实际矩阵快速幂的次数应为 \(\lceil\frac{n}{2}\rceil + 1\),原因同上文注意点)。
对于不合法的情况还需要修正一下这个矩阵,首先和原偶数时的矩阵的不同处是最后一步的特殊情况只针对于 \(l + 1 = r, s_l = s_r\) 的情况,所以应该再得到一个 \(g'_{c_{\operatorname{A}}}\) 表示在原转移图中经过了 \(c_{\operatorname{A}}\) 个点且最后一步是 \(l + 1 = r, s_l = s_r\) 的方案数,改为 \(p_{\operatorname{A}, c_{\operatorname{A}}}\stackrel{g'_{c_A}}{\to} p_{\operatorname{B}, c_{\operatorname{B}}}\)
还有一个不同处时这个方案必须要满足倒数第二步做完还在 \(p_{\operatorname{B}, 1}\) 处,最后一步恰好走到 \(p_{\operatorname{B}, 0}\) 处(终点),所以应当要满足到 \(p_{\operatorname{B}, 0}\) 时恰好为 \(\lceil\frac{n}{2}\rceil\) 步,对此只需要改成 \(p_{\operatorname{B}, 0}\stackrel{0}{\to}p_{\operatorname{B}, 0}\) 取消自环就可以了。
一样的是走 \(\lceil\frac{n}{2}\rceil\) 步。

于是就可以在 \(\mathcal{O}(m^3\log n)\) 的复杂度内解决这个问题了。

卡常方法:考虑以 \(p_{\operatorname{A}, 0}\to p_{\operatorname{A}, m}\to p_{\operatorname{B}, \lceil\frac{m}{2}\rceil}\to p_{\operatorname{B}, 0}\) 的顺序编号,那么矩阵只可能在满足 \(i\le j\)\((i, j)\) 处有值,矩阵乘法中 \(a_{i, j}b_{j, k}\to c_{i, k}\) 只需要考虑 \(i\le j\le k\) 的情况,就带了一个 \(\frac{1}{6}\) 的常数。

构造矩阵那里写的可能不是很像人能看的(。

#include<bits/stdc++.h>
using uint = unsigned;
constexpr int N = 303;
using arr = std::array<uint, N>;
using mat = std::array<arr, N>;
constexpr uint mod = 10007;
inline void add(uint &x, const uint y) { (x += y) >= mod && (x -= mod); }
char str[210];
uint cnt[202][202][202], fr[202];
inline mat mul(const mat &x, const mat &y) {
    mat z = {};
    for (int i = 0; i < N; i++) {
        for (int k = i; k < N; k++) {
            for (int j = i; j <= k; j++) {
                add(z[i][k], x[i][j] * y[j][k] % mod);
            }
        }
    }
    return z;
}
inline mat qpow(mat a, int b) {
    mat val = {};
    for (int i = 0; i < N; i++) val[i][i] = 1;
    for (; b; b >>= 1, a = mul(a, a)) {
        if (b & 1) val = mul(val, a);
    }
    return val;
}
int main() {
    int n, m;
    scanf("%s%d", str + 1, &n), m = strlen(str + 1);
    cnt[1][m][0] = 1;
    for (int l = 1; l <= m; l++) {
        for (int r = m; r >= l; r--) {
            if (str[l] != str[r]) {
                for (int c = m; c >= 1; c--) cnt[l][r][c] = cnt[l][r][c - 1];
                cnt[l][r][0] = 0;
            }
            if (str[l] == str[r] && r <= l + 1) {
                for (int c = 0; c <= m; c++) add(fr[c], cnt[l][r][c]);
            } else if (str[l] == str[r]) {
                for (int c = 0; c <= m; c++) add(cnt[l + 1][r - 1][c], cnt[l][r][c]);
            } else {
                for (int c = 0; c <= m; c++) add(cnt[l + 1][r][c], cnt[l][r][c]);
                for (int c = 0; c <= m; c++) add(cnt[l][r - 1][c], cnt[l][r][c]);
            }
        }
    }
    mat a = {};
    for (int i = 0; i < m; i++) a[i][i + 1] = 1;
    for (int i = 1; i <= m; i++) a[i][i] = 24;
    for (int i = 0; i <= (m + 1) / 2; i++) a[m + 1 + i][m + 1 + i + 1] = 1;
    for (int i = 0; i <= (m + 1) / 2; i++) a[m + 1 + i][m + 1 + i] = 25;
    a[m + 1 + (m + 1) / 2 + 1][m + 1 + (m + 1) / 2 + 1] = 26;
    for (int i = 0; i <= m; i++) a[i][m + 1 + (m + 1) / 2 + 1 - (m - i + 1) / 2] = fr[i];
    uint ans = qpow(a, (n + m + 1) / 2 + 1)[0][m + 1 + (m + 1) / 2 + 1];
    if ((n + m) % 2 == 0) return printf("%u\n", ans), 0;
    memset(fr, 0, sizeof(fr));
    for (int i = 1; i < m; i++) {
        if (str[i] == str[i + 1]) {
            for (int c = 0; c <= m; c++) add(fr[c], cnt[i][i + 1][c]);
        }
    }
    a = {};
    for (int i = 0; i < m; i++) a[i][i + 1] = 1;
    for (int i = 1; i <= m; i++) a[i][i] = 24;
    for (int i = 0; i <= (m + 1) / 2; i++) a[m + 1 + i][m + 1 + i + 1] = 1;
    for (int i = 0; i <= (m + 1) / 2; i++) a[m + 1 + i][m + 1 + i] = 25;
    for (int i = 0; i <= m; i++) a[i][m + 1 + (m + 1) / 2 + 1 - (m - i + 1) / 2] = fr[i];
    add(ans, mod - qpow(a, (n + m + 1) / 2 + 1)[0][m + 1 + (m + 1) / 2 + 1]);
    printf("%u\n", ans);
    return 0;
}
posted @ 2025-04-13 21:24  rizynvu  阅读(27)  评论(0)    收藏  举报