ZR955 折纸 (Manacher + hash)

Description

在这里插入图片描述

Solution

横着折纸不会影响和竖着折纸的方案数,竖着折纸也不会影响横着折纸的方案数,所以答案是横着折纸的方案数 × \times × 竖着折纸的方案数。

可以在横着折纸时 n n n 行看作一行,竖着折纸是 m m m 列看作一列,哈希一下就好了。那么问题转换成了只有一行或一列时的方案数,下面讨论一行的,一列同理。

先将折纸转换为,在一行字符串上切一下,令切的位置为 x x x,然后将短的折向长的,令短的长度为 k k k,有以 x x x 为回文中心的最长回文半径 ≥ k \ge k k。对于以每个点为回文中心的最长回文半径可以用 manacher 预处理。

折纸可能是从左往右,也可能是从右往左,当找到了从左往右折纸的一个方案时,那么这个方案是一定依赖于前面的方案,所以方案数加一。

但是从右往左的方案不依赖于在它之前从左往右的方案。一种从右往左的方案可以与在它之前能够折出来的所有从左往右的方案匹配。

可以预处理出在每个位置从左往右和从右往左,能不能在这个位置切一下对折,然后对于字符串的所有位置从左往右依次统计方案数即可。时间复杂度为 O ( n × m ) O(n \times m) O(n×m),瓶颈在于读入。

get 了 manacher 和 hash 的神奇用法。

Code

#include <bits/stdc++.h>
using namespace std;
#define re register
#define F first
#define S second
typedef long long ll;
typedef pair<int, int> P;
const int N = 1e6 + 5, Base = 233;
const int INF = 0x3f3f3f3f;
int read() {
	int x = 0, f = 0; char ch = 0;
	while (!isdigit(ch)) f |= ch == '-', ch = getchar();
	while (isdigit(ch)) x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
	return f ? -x : x;
}
int n, m, hsa[N], hsb[N], a[N], p[N];
bool l[N], r[N];
char s[N];
void manacher(int n, int b[]) {
	for (int i = 1; i <= n; i++) {
		a[i * 2] = b[i], a[i * 2 + 1] = -1;
	}
	a[0] = -2, a[1] = -1, a[2 * n + 3] = -2;
	int id = 0, mx = 0;
	for (int i = 1; i <= n * 2 + 1; i++) {
		p[i] = i < mx ? min(p[2 * id - i], mx - i) : 1;
		while (a[i - p[i]] == a[i + p[i]]) p[i]++;
		if (mx < i + p[i]) id = i, mx = i + p[i];
	}
	for (int i = 0; i <= n; i++) p[i] = p[i * 2 + 1] >> 1;
}
ll solve(int n, int b[]) {
	memset(p, 0, sizeof(p)); memset(l, 0, sizeof(l)); memset(r, 0, sizeof(r));
	manacher(n, b);
	int k = 0;
	for (int i = 1; i < n; i++) if (i - p[i] <= k) l[i] = 1, k = i;
	k = n;
	for (int i = n - 1; i; i--) if (i + p[i] >= k) r[i] = 1, k = i;
	k = 1;
	ll ans = 0;
	for (int i = 1; i <= n; i++){
		if (r[i]) ans += k; 
		if (l[i]) k++;
	}
	return ans + k;
}
int main() {
	n = read(), m = read();
	for (int i = 1; i <= n; i++) {
		scanf("%s", s + 1);
		for (int j = 1; j <= m; j++) {
			hsa[i] = hsa[i] * Base + s[j] - 48;
			hsb[j] = hsb[j] * Base + s[j] - 48;
		}
	}
	printf("%lld\n", solve(n, hsa) * solve(m, hsb));
	return 0;
}
posted @ 2020-02-20 10:45  ylxmf2005  阅读(34)  评论(0)    收藏  举报