2018 ICPC Nanjing M. Mediocre String Problem(哈希+二分+马拉车+差分)

题目链接

题意: 给你两个字符串 s s s t t t,对于 t t t中所有的前缀串 k k k,你要在 s s s中找到所有的子串 s i j s_{ij} sij,使得 s i j s_{ij} sij长度大于 k k k,并且将 s i j s_{ij} sij k k k拼接后得到的字符串为回文串。问你最终的数量和。

题解:为了让最后的字符串是回文串,对于前缀串 k k k,我们要先在 s s s中找到与前缀串 k k k一样的子串,再向后扩展,使得扩展的那部分字符串为回文串,即可实现。
我们先将 t t t进行反转,然后去遍历 s s s,那么现在的问题就转换成了找两个字符串的最长后缀(因为对于这部分所有的子串,他们的答案是一样的,所以最后求出来乘以最长后缀的长度就行了)。这里我们用哈希+二分来实现。
找出最长后缀后,我们假设当前遍历到 s i s_i si,那么接下来我们要求以 s i + 1 s_{i+1} si+1为起点的回文串数量。想到回文串就想到了马拉车。马拉车可以求以每个点为中心的最长回文半径,那么对于以这个点为终点,以 i i i - 最长回文半径的位置为起点的区间,终点对于这段区间的贡献值为 1 1 1。所以就转化成了区间修改,最终求每个点的权值的差分数组问题。稍微推一下就能从马拉车字符串推回到原字符串。

#include <bits/stdc++.h>
using namespace std;

typedef unsigned long long ull;
typedef long long ll;

const int maxn=1e6 + 7;
const ull base = 131;
char s[maxn], t[maxn];
char ma[maxn<<1];
ll mp[maxn<<1], paw[maxn];
ull gap[maxn], hashS[maxn], hashT[maxn];

void init() {
	gap[0] = 1;
	for (int i = 1; i <= 1000000; ++i) {
		gap[i] = gap[i - 1] * base;
	}
}

void makeHash(char *s, ull *hash) {
	int len = strlen(s);
	for (int i = 0; i < len; ++i) {
		hash[i + 1] = hash[i] * base + (ull)s[i];
	}
}

ull getHash(int l, int r, ull *hash) {
	return hash[r + 1] - hash[l] * gap[r - l + 1];
}

void manacher(char *s){
	int len = strlen(s);
	int l=0;
	ma[l++]='$';
	ma[l++]='#';
	for(int i = 0;i < len; ++i){
		ma[l++] = s[i];
		ma[l++] = '#';
	}
	ma[l] = 0;
	ll mx = 0, id = 0;
	for (int i = 0; i < l; ++i){
		mp[i] = mx > i? min(mp[2 * id - i], mx - i) : 1;
		while (ma[i + mp[i]] == ma[i - mp[i]])	mp[i]++;
		if(i + mp[i] > mx){
			mx = i + mp[i];
			id = i;
		}
	}
	for (int i = 1; i < l; i++) {
		paw[(i >> 1) - (mp[i] >> 1)]++;
		paw[(i >> 1)]--;
	}
	for (int i = 1; i < len; ++i) {
		paw[i] += paw[i - 1];
	}
}

void solve() {
	int len = strlen(s);
	int lent = strlen(t);
	int l, r, mid;
	ll ans = 0;
	ll maxx;
	for (int i = 0; i < len - 1; ++i) {
		l = 1, r = i + 1;
		maxx = 0;
		while (l <= r) {
			mid = l + r >> 1;
			if (getHash(i - mid + 1, i, hashS) == getHash(lent - mid, lent - 1, hashT)) {
				maxx = mid;
				l = mid + 1;
			}
			else r = mid - 1;
		}
		ans += maxx * paw[i + 1];
	}
	printf("%lld\n", ans);
}

int main(){
	init();
	scanf("%s %s", s, t);
	reverse(t, t + strlen(t));
	manacher(s);
	makeHash(s, hashS);
	makeHash(t, hashT);
	solve();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值