【题解】[JSOI2016] 扭动的回文串

P4324 JSOI2016 扭动的回文串 - 洛谷 (luogu.com.cn)

想了很久 Manacher 解法没成功,一看题解可以用纯哈希过。


前两种情况无论你使用哈希还是马拉车,都可以很快解决,所以着重看第三种情况。

回文串相关的题目一定和枚举中心点相关,往这方面想。

如何在确定中心点 的情况下,让两个串的匹配最大化?

假设当前中心点在 A,如果和中心点连着的一段下标对应的字符 A 串和 B 串的一样,

我们优先选 A 的,反之优先选 B 的。

如何让这种"连着"最大化?那其实就是在找在 A 中以 i 为中心的最长回文半径。

以这个半径作为固定要选在 A 的字符,往左边向 A 拓展,往右边向 B 拓展,看是否能匹配。

这个匹配的过程可以现用现算,二分就行。

可以证明这样一定是最优解之一,中心点在 B 同理,偶数情况也不难。

由于这个做法一定带一个 log,所以求回文半径也不用马拉车了,一样用二分哈希。


怎么说呢?是一个比较巧妙的解法吧,第一次看有点想不到,但其实思路挺自然。

最主要是想到回文半径的基础上配对一定是最优解之一。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

typedef unsigned long long ULL;   // 自然溢出 
const int N = 1e5 + 10;
const ULL P = 13331;   // 哈希基数 

int n;
char sa[N], sb[N];
ULL ha[N], hb[N], rha[N], rhb[N];
ULL facp[N];

void init() {
	ha[0] = 0; hb[0] = 0;
	for (int i = 1; i <= n; i ++) {
		ha[i] = ha[i - 1] * P + (sa[i] - 'A' + 1);
		hb[i] = hb[i - 1] * P + (sb[i] - 'A' + 1);
	}
	rha[n + 1] = 0; rhb[n + 1] = 0;
	for (int i = n; i >= 1; i --) {
		rha[i] = rha[i + 1] * P + (sa[i] - 'A' + 1);
		rhb[i] = rhb[i + 1] * P + (sb[i] - 'A' + 1);
	}
	facp[0] = 1;
	for (int i = 1; i <= n + 1; i ++) {
		facp[i] = facp[i - 1] * P;
	}
}

ULL get_h(ULL h[], int l, int r) {
	return h[r] - h[l - 1] * facp[r - l + 1];
}

ULL get_rh(ULL rh[], int l, int r) {
	return rh[l] - rh[r + 1] * facp[r - l + 1];
}

int get_expand(ULL h[], ULL rh[], int ql, int qr) {
	int l = 0, r = min(ql, n - qr + 1), res = l;
	while (l <= r) {
		int mid = (l + r) >> 1;
		ULL ta = get_h(h, ql - mid + 1, ql);
		ULL tb = get_rh(rh, qr, qr + mid - 1);
		if (ta == tb) {
			l = mid + 1;
			res = mid;
		}
		else {
			r = mid - 1;
		}
	}
	return res;
} 

int main () {
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	cin >> n;
	cin >> (sa + 1) >> (sb + 1);
	
	init();
	
	int ans = 1;
	
	for (int i = 2; i <= n; i ++) {
		int la = get_expand(ha, rha, i, i);
		int lb = get_expand(hb, rhb, i, i);
		ans = max(ans, la * 2 - 1 + get_expand(ha, rhb, i - la, i + la - 1) * 2);
		// 在 A 里 [i - la + 1, i + la - 1] 都是回文半径,B 的第一个和 A 的最后一个共用一个下标 
		ans = max(ans, lb * 2 - 1 + get_expand(ha, rhb, i - lb + 1, i + lb) * 2);
		// 在 B 里 [i - lb + 1, i + lb - 1] 都是回文半径,A 的最后一个和 B 的第一个共用一个下标 
	}
	
	for (int i = 1; i < n; i ++) {
		int la = get_expand(ha, rha, i, i + 1);
		int lb = get_expand(hb, rhb, i, i + 1);
		ans = max(ans, la * 2 + get_expand(ha, rhb, i - la, i + la) * 2);
		// 在 A 里 [i - la + 1, i + la] 都是回文半径,B 的第一个和 A 的最后一个共用一个下标 
		ans = max(ans, lb * 2 + get_expand(ha, rhb, i - lb + 1, i + lb + 1) * 2);
		// 在 B 里 [i - lb + 1, i + lb] 都是回文半径,A 的最后一个和 B 的第一个共用一个下标 
	}
	
	cout << ans << "\n";
	
	return 0;
}
相关推荐
Lyyaoo.2 小时前
【二分查找】【困难】寻找两个有序数组的中位数
java·数据结构·算法
有点。2 小时前
C++03阶段练习(一)
数据结构·c++
刃神太酷啦2 小时前
Redis 核心进阶:哨兵、集群、缓存问题与分布式锁详解----《Hello Redis!》(6)
linux·c语言·数据库·c++·redis·分布式·缓存
郝学胜-神的一滴3 小时前
C++20模板元编程 01:从零吃透模板核心底层逻辑
开发语言·c++·vscode·程序人生·开源
kevin_kang3 小时前
第05章 实时语音识别的 ASR 原理
算法
kevin_kang3 小时前
第04章 断句原理:本地 VAD 与神经网络 VAD
算法
kevin_kang3 小时前
第06章 文字转语音的 TTS 原理
算法
6Hzlia3 小时前
【Classic 150 刷题计划】 LeetCode 123. 买卖股票的最佳时机 III | C++ 状态机动态规划与通用交易拓展
c++·算法·leetcode
luj_17683 小时前
生物体如何应对功能击穿?
c语言·开发语言·c++·经验分享·算法