1. 程式人生 > >[bzoj3160]萬徑人蹤滅_FFT_Manacher

[bzoj3160]萬徑人蹤滅_FFT_Manacher

萬徑人蹤滅 bzoj-3160

題目大意:給定一個ab串。求所有的子序列滿足:位置和字元都關於某條對稱軸對稱而且不連續。

註釋:$1\le n\le 10^5$。


想法

看了大爺的題解,OrzOrz。

因為對稱軸可以是兩個字元中間的位置,所以我們把字串按照$Manacher$的形式倍增。

我們希望處理出一個數組$f$,$f_i$表示以$i$為對稱軸的左右相等字元個數。

以當前位置為對稱軸的答案顯然就是$2^{f_i}-1$。

因為還有不連續的條件,我們只需要減掉$Manacher$的迴文半徑即可。

現在考慮如何能求出$f$陣列。

不難發現:其實原序列中的第$i$個字元如果和第$j$個字元相等那麼會更新到倍增序列後的第$i+j$個字元。

所以$f_i=((\sum\limits_{j=0}^{i-1} (s[j]==s[i-j]))+1)/2$。

看起來像卷積的形式,想到$FFT$。

但是$FFT$只能做乘法,這個題怎麼辦?

我們先把所有的$a$字元都變成$1$,$b$字元變成$0$,然後統計$((\sum\limits_{j=0}^{i-1} a[j]\cdot a[i-j])+1)/2$。

再把所有的$b$字元變成$1$,$a$字元變成$0$。

用$FFT$優化卷積即可。

Code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define mod 1000000007 
#define N 100010 
using namespace std;
typedef long long ll;
typedef double db;
const db pi=acos(-1);
struct cp
{
	db x,y;
	cp() {x=y=0;}
	cp(db x_,db y_){x=x_,y=y_;}
	cp operator + (const cp &a) const {return cp(x+a.x,y+a.y);}
	cp operator - (const cp &a) const {return cp(x-a.x,y-a.y);}
	cp operator * (const cp &a) const {return cp(x*a.x-y*a.y,x*a.y+y*a.x);}
}a[N<<2];
void fft(cp *a,int len,int flg)
{
	int i,j,k,t;
	cp w,wn,tmp;
	for(i=k=0;i<len;i++)
	{
		if(i>k) swap(a[i],a[k]);
		for(j=len>>1;(k^=j)<j;j>>=1);
	}
	for(k=2;k<=len;k<<=1)
	{
		t=k>>1;
		wn=cp(cos(2*pi*flg/k),sin(2*pi*flg/k));
		for(i=0;i<len;i+=k)
		{
			w=cp(1,0);
			for(j=i;j<i+t;j++)
			{
				tmp=a[j+t]*w;
				a[j+t]=a[j]-tmp;
				a[j]=a[j]+tmp;
				w=w*wn;
			}
		}
	}
	if(flg==-1) for(i=0;i<len;i++) a[i].x/=len;
}
char s[N],t[N<<1];int f[N<<1],n,m,c[N<<2],ans,p[N<<2];
void manacher()
{
	for(int i=0;i<n;i++)
	{
		t[++m]='#';
		t[++m]=s[i];
	}
	t[++m]='#';
	f[1]=0;
	for(int now=1,i=1;i<=m;i++)
	{
		f[i]=min(f[(now<<1)-i],now+f[now]-i);
		for(;i+f[i]<m&&i-f[i]>1;f[i]++) if(t[i+f[i]+1]!=t[i-f[i]-1]) break;
		if(i+f[i]>now+f[now]) now=i;
		ans-=(f[i]+1)>>1;
		ans%=mod;
	}
}
int main()
{
	scanf("%s",s); n=strlen(s);
	int len=1; while(len<(n<<1))len<<=1;
	for(int i=0;i<n;i++) a[i].x=(s[i]=='a');
	fft(a,len,1);
	for(int i=0;i<len;i++) a[i]=a[i]*a[i];
	fft(a,len,-1);
	for(int i=0;i<len;i++) c[i]=(int)(a[i].x+0.1),a[i]=cp();
	for(int i=0;i<n;i++) a[i].x=(s[i]=='b');
	fft(a,len,1);
	for(int i=0;i<len;i++) a[i]=a[i]*a[i];
	fft(a,len,-1);
	for(int i=0;i<len;i++) c[i]+=(int)(a[i].x+0.1);
	manacher();
	p[0]=1;
	for(int i=1;i<len;i++) p[i]=(p[i-1]<<1)%mod;
	for(int i=0;i<len;i++) ans=(ans+p[(c[i]+1)>>1]-1)%mod;
	printf("%d\n",(ans+mod)%mod);
	return 0;
}

小結:好題好題。比那個什麼孤舟蓑笠翁友善多了。

  這個題主要是需要想到求$f$陣列。而且連續的話需要用$Manacher$減掉,非常好的一道題。