COI2016 Palinilap(manacher+后缀数组)

原创 2016年05月31日 22:04:18

题意:给出一个小写字母组成的字符串,修改一个字符(或者不改),使得字符串中所有回文串的长度之和最大(本质相同的回文串算多个)。

好神的题。。题意简单易懂但是做起来非常麻烦。。

以后如果只是求求lcp之类的最好写二分+hash,写个后缀数组简直是自讨苦吃。。

很显然回文串的长度之和就是manacher算法中半径数组之和。

修改一个字符有可能破坏一些回文串,也有可能引入一些新的回文串,只要我们能对每个字符处理出这两个值即可。

先考虑破坏的。考虑一个点,以它为中心扩展出的极大回文串内如果有字母被修改,显然会损失这个点的半径值,且损失程度为从中间向两边递减的公差为1的等差数列。可以用线段树来加,也可以离线+前缀和。

再考虑增加的。对一个点而言,记l和r分别为这个点所能扩展的极大回文串的左右端点。如果l=1或者r=n,则显然这个点半径值无论如何也不能增大了。否则要让这个点的半径值增大,就必须将s[l-1]改为s[r+1]或者将s[r+1]改为s[l-1],修改之后这个点的半径值就可以增加s[r+1]向右和s[l-1]向左所能匹配的最大长度。这个可以将原串逆序后放在后面求一次后缀数组之和用lcp来实现。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define LL long long
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define erp(i,a,b) for(int i=a;i>=b;--i)
using namespace std;
const double alp = 0.75;
const LL INF = 1ll<<61;
const int MAXN = 200005;

int N;
char s[MAXN];
int olen[MAXN], elen[MAXN];
void manacher()
{
	static char tmp[MAXN*2];
	static int p[MAXN];
	int M = N*2 + 1;
	for (int i = 0; i<M; ++i) tmp[i] = '#';
	for (int i = 0; i<N; ++i) tmp[i*2+1] = s[i+1];
	for (int i = 0, j = 0, k; i<M; )
	{
		while (i-j>=0 && i+j<M && tmp[i-j]==tmp[i+j]) ++j;
		p[i] = j;
		for (k = 1; p[i-k] < p[i]-k; ++k) p[i+k] = p[i-k];
		i += k, j = max(0, j-k);
	}
	rep(i, 1, N) olen[i] = p[(i-1)*2+1]>>1;
	rep(i, 1, N) elen[i-1] = p[(i-1)*2]>>1;
}

int sa[MAXN], rnk[MAXN], height[MAXN], f[20][MAXN], lg2[MAXN];
#define lch(a) tr[a].ch[0]
#define rch(a) tr[a].ch[1]
int ncnt, rt, len, gt, gtf, tmr;
LL lpos, rpos;
struct Node {
	int ch[2], c, sa, sz;
	LL key;
} tr[MAXN];
int pt[MAXN], pn;
inline int cmp(int x, int y)
{
	if (tr[x].c^tr[y].c) return tr[x].c < tr[y].c;
	return tr[x-1].key < tr[y-1].key;
}
inline void pushup(int x)
{
	tr[x].sz = tr[lch(x)].sz + tr[rch(x)].sz + 1;
}
inline bool isbad(int x)
{
	return max(tr[lch(x)].sz, tr[rch(x)].sz) > tr[x].sz*alp;
}
void treavel(int x)
{
	if (!x) return;
	treavel(lch(x)), pt[++pn] = x, treavel(rch(x));
}
int build(int L, int R, LL l, LL r)
{
	if (L>R) return 0;
	int md = (L+R)>>1;
	int x = pt[md];
	tr[x].key = l+r;
	LL mid = (l+r)>>1;
	lch(x) = build(L, md-1, l, mid);
	rch(x) = build(md+1, R, mid, r);
	return pushup(x), x;
}
void ins(int&x, LL l, LL r)
{
	if (!x) { x = ncnt; tr[x].key = l+r; return; }
	LL mid = (l+r)>>1;
	int d = cmp(x, ncnt);
	if (!d) ins(lch(x), l, mid);
	else ins(rch(x), mid, r);
	if (pushup(x), isbad(x)) gt = x, gtf = 0, lpos = l, rpos = r;
	else if (gt==lch(x)||gt==rch(x)) gtf = x;
}
void extend(int c, int p)
{
	++ncnt, tr[ncnt].sa = p, tr[ncnt].c = c;
	gt = pn = 0, ins(rt, 1, INF);
	if (!gt) return;
	treavel(gt);
	if (!gtf) rt = build(1, pn, 1, INF);
	else if (gt==lch(gtf)) lch(gtf) = build(1, pn, lpos, rpos);
	else rch(gtf) = build(1, pn, lpos, rpos);
}
void dfs(int u)
{
	if (!u) return;
	dfs(lch(u)), sa[++tmr] = tr[u].sa, dfs(rch(u));
}
void MakeST()
{
	rep(i, 1, N) f[0][i] = height[i];
	rep(j, 1, 18) rep(i, 1, N-(1<<j)+1) f[j][i] = min(f[j-1][i], f[j-1][i+(1<<(j-1))]);
}
inline int quary(int l, int r)
{
	int t = lg2[r - l + 1];
	return min(f[t][l], f[t][r - (1<<t) + 1]);
}
inline int lcp(int a, int b)
{
	if (a == b) return N*2 - a + 1;
	a = rnk[a], b = rnk[b];
	if (a > b) swap(a, b);
	return quary(a+1, b);
}
void makesa()
{
	rep(i, 1, N) extend(s[i], 2*N-i+1);
	erp(i, N, 1) extend(s[i], i);
	dfs(rt);
	rep(i, 1, N) s[i+N] = s[N-i+1];
	N*=2;
	rep(i, 1, N) rnk[sa[i]] = i;
	rep(i, 2, N) lg2[i] = lg2[i>>1]+1;
	for (int i = 1, j = 0; i<=N; ++i, j&&--j)
	{
		if (rnk[i]==1) continue;
		while (i+j<=N && sa[rnk[i]-1]+j<=N && s[i+j]==s[sa[rnk[i]-1]+j]) ++j;
		height[rnk[i]] = j;
	}
	MakeST();
	N/=2;
}

LL dv[MAXN], av[26][MAXN];
LL ad[MAXN], adr[MAXN], as[MAXN], asr[MAXN];
void addltr(int l, int r)
{
	ad[l]++, ad[r+1]--;
	as[r+1] -= r-l+1;
}
void addrtl(int l, int r)
{
	l = N-l+1, r = N-r+1, swap(l, r);
	adr[l]++, adr[r+1]--;
	asr[r+1] -= r-l+1;
}
void calsum()
{
	rep(i, 1, N)
	{
		ad[i] += ad[i-1], adr[i] += adr[i-1];
		as[i] += as[i-1], asr[i] += asr[i-1];
	}
	rep(i, 1, N) ad[i] += ad[i-1], adr[i] += adr[i-1];
	reverse(adr+1, adr+N+1);
	reverse(asr+1, asr+N+1);
	rep(i, 1, N) dv[i] = ad[i]+as[i]+adr[i]+asr[i];
}

int getvalue(int l, int r)
{
	if (l<1||r>N) return 0;
	int res = lcp(r, 2*N-l+1);
	res = min(res, min(l, N-r+1));
	return res;
}

void calcLoss()
{
	for (int i = 1, l, r; i<=N; ++i)
	{
		if (olen[i]!=1)
		{
			l=i-olen[i]+1, r=i+olen[i]-1;
			addltr(l, i-1), addrtl(i+1, r);
		}
		if (elen[i]!=0)
		{
			l=i-elen[i]+1, r=i+elen[i];
			addltr(l, i), addrtl(i+1, r);
		}
	}
}

void calcValue()
{
	for (int i = 1, l, r, len; i<=N; ++i)
	{
		l = i-olen[i]+1, r = i+olen[i]-1;
		if (l!=1 && r!=N)
		{
			l --, r ++;
			len = getvalue(l-1, r+1);
			av[s[r]-'a'][l] += len+1;
			av[s[l]-'a'][r] += len+1;
		}
		l = i-elen[i]+1, r = i+elen[i];
		if (l!=1 && r!=N)
		{
			l --, r ++;
			len = getvalue(l-1, r+1);
			av[s[r]-'a'][l] += len+1;
			av[s[l]-'a'][r] += len+1;
		}
	}
}

int main()
{
	scanf("%s", s+1);
	N = strlen(s+1);
	manacher();
	makesa();
	calcLoss();
	calsum();
	calcValue();
	LL sum = 0;
	rep(i, 1, N) sum += elen[i]+olen[i];
	LL ans = sum;
	rep(i, 1, N) rep(j, 0, 25)
		ans = max(ans, sum - dv[i] + av[j][i]);
	cout << ans << '\n';
	return 0;
}


版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

UVA 11475 Extend to Palindrome (kmp || manacher || 后缀数组)

题目链接:点击打开链接 题意:给你一个串,让你在串后面添加尽可能少的字符使得这个串变成回文串。 思路:这题可以kmp,manacher,后缀数组三种方法都可以做,kmp和manacher效率较高,...

HDU 4426 (ZOJ 3661) Palindromic Substring 后缀数组二分 + Manacher + Hash

题目大意: 就是现在对于T(T 大致思路: 首先用Manacher算法处理出各个字符为中心的回文半径, 然后由于一个长度为n的字符串中最多只有O(n)个不同的回文串(其实位置不同但序列...

(CDOJ) UESTC 606 Palindrome Again 后缀数组二分 + Manacher + Hash

题目大意: 就是现在给出两个只包含小写字母的字符串A, B, 一个正整数d, 求三元组(i, j, k)满足A[i, i + 1, .... i + k - 1] == B[j, j + 1, .....

URAL 1297 Palindrome 后缀数组 或 Manacher 求最长回文子串

题目大意: 就是给出一个长度不超过1000个只包含大小写英文字母的字符串,输出其最长回文子串 大致思路: 首先很容易想到用将该字符串本身反转之后与自己连接起来, 中间用一个未出现的字符隔开, 求出...

URAL 1297(后缀数组/Manacher算法)

(http://acm.hust.edu.cn/vjudge/contest/view.action?cid=105595#problem/F) 题意:寻找最长回文子串 解法:Manacher裸题...

2016计蒜客初赛第三场 百度帐号的选取方案(困难):后缀数组+循环节+线段树

题目链接:http://nanti.jisuanke.com/t/11144 题目概述:给定一个字符串,求不相交的两个最大循环节相同的子串的个数。 解题思路:后缀数组+循环节+线段树...

4516: [Sdoi2016]生成魔咒|后缀数组|线段树|ST表

将原串倒过来,每次添加一个字符相当于增加一个后缀。 问题转化为向集合中动态添加后缀求本质不同的字串的个数,离线求出SASA 找出当前添加的串与集合中的串的最大的LCPLCP,就是重复出现的子串的个...
  • ws_yzy
  • ws_yzy
  • 2016年04月15日 08:22
  • 937

NOI2016 优秀的拆分 [后缀数组]

如果一个字符串可以被拆分为AABBAABBAABBAABB的形式,其中AAAA和BBBB是任意非空字符串,则我们称该字符串的这种拆分是优秀的。 例如,对于字符串aabaabaaaabaabaa如果令...

【后缀数组】[NOI2016]优秀的拆分

题目描述如果一个字符串可以被拆分为 AABBAABB 的形式,其中 AA 和 BB 是任意非空字符串,则我们称该字符串的这种拆分是优秀的。例如,对于字符串 aabaabaa,如果令 A=aabA = ...

bzoj 4516: [Sdoi2016]生成魔咒 后缀数组

将整个数组反过来,那么每次就相当于查询后缀[i,n]中有多少本质不同的子串。那么就可以建出后缀数组,然后新加入一个后缀[p],那么首先需要得到这个后缀中有多少子串是重复的,相当于求这个后缀与后缀集合[...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:COI2016 Palinilap(manacher+后缀数组)
举报原因:
原因补充:

(最多只允许输入30个字)