1. 程式人生 > >BZOJ2084[Poi2010]Antisymmetry——回文自動機

BZOJ2084[Poi2010]Antisymmetry——回文自動機

它的 lld bsp 回文自動機 long its fin set algo

題目描述

對於一個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);
}

BZOJ2084[Poi2010]Antisymmetry——回文自動機