2025.05.13 CW 模拟赛 C. 折纸
C. 折纸
题目描述
思路
先考虑一下部分分, 也就是只有 \(1\) 行的情况.
思考一下折叠的本质: 这个操作事实上就是将某一个回文前缀/后缀的一半删除, 同时这个回文的长度一定是偶数. 例如 abbac, 其前缀有回文 abba, 折叠一下就成了 bac, 如果后缀有也同理.
前缀和后缀是等价的, 我们就讨论一下前缀如何处理. 我们定义 \(f_i\) 表示是否存在一种操作, 使得 \([1, i)\) 的字符被删除, 亦即保留 \([i, n]\). 初始化 \(f_1 = 1\). 考虑转移, 我们记 \(p_i\) 表示以 \(i - 1, i\) 为回文中心的最长回文半径, 显然对于半径 \(r \in [1, p_i]\) 的字串一定都是回文的, 也就是能够从 \(f_{i - r}\) 转移过来, 我们记一个前缀即可 \(\mathcal{O}(1)\) 转移.
最后统计答案, 相当于统计有多少个 \([l, r]\) 是合法的, 即 \(l \le r, f_l = 1, g_r = 1\). 我们定义 \(suf = \sum g_i\), 接着从前往后扫, \(ans := ans + f_i \times suf, suf := suf - g_i\).
上面的讨论都是基于只有 \(1\) 行的情况, 但是对于矩阵同样适用. 具体来说, 我们将每一列/行哈希成一个数值, 然后就成了 \(1\) 行的情况, 最后将行和列的答案乘起来即可.
#include <iostream>
#include <string>
#include <vector>
using namespace std;
typedef unsigned long long ull;
constexpr int N = 1000010;
constexpr ull P = 131;
int n, m, p[N << 1];
vector<string> s;
vector<ull> deal(vector<ull> v) {
vector<ull> res;
res.push_back(0), res.push_back(1);
for (ull x : v) {
res.push_back(x), res.push_back(1);
}
res.push_back(0);
return res;
}
void manacher(vector<ull> v) {
int r = 0, c = 0, sz = v.size() - 1;
for (int i = 1; i <= sz; ++i) {
if (i < r) {
p[i] = min(p[c * 2 - i], p[c] + c - i);
}
else {
p[i] = 1;
}
while (v[i + p[i]] == v[i - p[i]]) {
++p[i];
}
if (p[i] + i > r) {
r = p[i] + i, c = i;
}
}
}
class Row {
private:
int f[N], g[N];
public:
ull calc() {
vector<ull> H;
for (int i = 1; i <= m; ++i) {
ull num = 0;
for (int j = 1; j <= n; ++j) {
num = num * P + s[j][i];
}
H.push_back(num);
}
H = deal(H), manacher(H);
f[1] = g[m] = 1;
for (int i = 2; i <= m; ++i) {
int len = (p[i * 2 - 1] - 1) / 2;
f[i] = f[i - 1] + (f[i - 1] - f[i - len - 1] > 0);
}
for (int i = m - 1; i; --i) {
int len = (p[i * 2 + 1] - 1) / 2;
g[i] = g[i + 1] + (g[i + 1] - g[i + len + 1] > 0);
}
ull res = 0;
for (int i = 1; i <= m; ++i) {
res += (f[i] - f[i - 1]) * g[i];
}
return res;
}
} row;
class Column {
private:
int f[N], g[N];
public:
ull calc() {
vector<ull> H;
for (int i = 1; i <= n; ++i) {
ull num = 0;
for (int j = 1; j <= m; ++j) {
num = num * P + s[i][j];
}
H.push_back(num);
}
H = deal(H), manacher(H);
f[1] = g[n] = 1;
for (int i = 2; i <= n; ++i) {
int len = (p[i * 2 - 1] - 1) / 2;
f[i] = f[i - 1] + (f[i - 1] - f[i - len - 1] > 0);
}
for (int i = n - 1; i; --i) {
int len = (p[i * 2 + 1] - 1) / 2;
g[i] = g[i + 1] + (g[i + 1] - g[i + len + 1] > 0);
}
ull res = 0;
for (int i = 1; i <= n; ++i) {
res += (f[i] - f[i - 1]) * g[i];
}
return res;
}
} column;
void init() {
cin >> n >> m;
s.resize(n + 1);
for (int i = 1; i <= n; ++i) {
cin >> s[i], s[i] = ' ' + s[i];
}
}
void calculate() {
cout << row.calc() * column.calc() << '\n';
}
void solve() {
init();
calculate();
}
int main() {
solve();
return 0;
}
总结
多在题目中找性质. 对于字符串矩阵, 在某些情况下可以哈希成一行进行处理.


浙公网安备 33010602011771号