BZOJ2084[Poi2010]Antisymmetry——回文自动机

题目描述

对于一个01字符串,如果将这个字符串0和1取反后,再将整个串反过来和原串一样,就称作“反对称”字符串。比如00001111和010101就是反对称的,1001就不是。
现在给出一个长度为N的01字符串,求它有多少个子串是反对称的。

输入

第一行一个正整数N (N <= 500,000)。第二行一个长度为N的01字符串。

输出

一个正整数,表示反对称子串的个数。

样例输入

8
11001011

样例输出

7
hint
7个反对称子串分别是:01(出现两次), 10(出现两次), 0101, 1100和001011
 
对于一个回文串我们判定的方法是对称的位置字符相同,而反对称串则是要求对称的位置字符不同,所以在建回文自动机跳$fail$时只要将判断条件改一下即可。因为反对称串长度一定是偶数,所以不必建出奇回文树(当跳到$1$时就停止)。剩下的就是查询子串数了,一个点所代表的反对称串被所有$fail$指针直接或间接指向它的点所代表的反对称串包含,所以只需要求每个点在$fail$树上的子树权值和即可(因为回文自动机上每个点所代表的的回文串在原串中不一定出现一次,所以每个点的权值不同)。
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<cstdio>
#include<bitset>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
char s[500010];
int fail[500010];
int tr[500010][26];
int len[500010];
int n,p,q;
int last;
int cnt[500010];
int num;
ll ans;
int build(int x)
{
	len[++num]=x;
	return num;
}
int main()
{
	scanf("%d",&n);
	scanf("%s",s+1);
	fail[0]=1,len[1]=-1,num=1;
	for(int i=1;i<=n;i++)
	{
		int x=s[i]-'a';
		p=last;
		while((s[i-len[p]-1]==s[i]||i-len[p]-1==0)&&p!=1)
		{
			p=fail[p];
		}
		if(s[i-len[p]-1]==s[i]||i-len[p]-1==0)
		{
			last=0;
			continue;
		}
		if(!tr[p][x])
		{
			q=build(len[p]+2);
			int np;
			np=fail[p];
			while((s[i-len[np]-1]==s[i]||i-len[np]-1==0)&&np!=1)
			{
				np=fail[np];
			}
			if(s[i-len[np]-1]==s[i]||i-len[np]-1==0)
			{
				fail[q]=0;
			}
			else
			{
				fail[q]=tr[np][x];
			}
			tr[p][x]=q;
		}
		cnt[last=tr[p][x]]++;
	}
	for(int i=num;i>=1;i--)
	{
		cnt[fail[i]]+=cnt[i];
		ans+=1ll*cnt[i];
	}
	printf("%lld",ans);
}
posted @ 2019-02-22 14:04  The_Virtuoso  阅读(235)  评论(0编辑  收藏  举报