1. 程式人生 > >【FFT】【Manacher】【bitset】lydsy3160 萬徑人蹤滅

【FFT】【Manacher】【bitset】lydsy3160 萬徑人蹤滅

3160: 萬徑人蹤滅
Time Limit: 10 Sec Memory Limit: 256 MB
Submit: 2360 Solved: 1274
[Submit][Status][Discuss]
Description

a
b
c
d

Input

Output
Sample Input
Sample Output
HINT

Source
2013湖北互測week1


 題意見題面,不想再多敘述了,應該不是一道難題,但是菜雞寫了很久。感覺自己最近碼力不夠了,需要多加鍛鍊呀!
 第一個想法很簡單,把那些連續的段篩出,也就是用Manacher求個迴文串個數。不連續的段呢?對於每個對稱軸,我們把原字串和按軸反轉後的字串進行比較,統計相同的字元對(這些對就是之前關於於對稱軸的相同對)CNT,考慮每個對的選與不選,然後答案就是Σ((1<<CNT)-1),減去1是因為空集顯然不是答案。
 那麼,每次我們不斷移動串s和串的反轉rev,s與rev之間的相對位置不同就等價於對稱軸的不同。另外由於只有a,b兩種字元,可以用01來表示,用兩個串的同或的bitcount來求出CNT。最後用bitset壓縮位數,空間和效率就能快32倍了。
 bitset是一個很有用的容器,以後要多加掌握。測試過,速度遠比暴力快。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<bitset>
#define mo 1000000007
using namespace std;
 
char s[100005],tmp[200005];
int Len[200005],n,tn,pw[100005],ans;
bitset<100005> B,C,D;
 
int Mana_Init(char *s, int n)
{
    int i;
    tmp[0]='@';
    tmp[2*n+2]='$';
    tmp[2*n+
1]='#'; for(int i=1;i<=n*2;i+=2) tmp[i]='#',tmp[i+1]=s[i>>1]; return 2*n+1; } int Manacher(char *s, int n) { int mx=0,cnt=0,pos=0; for(int i=1;i<=n;i++) { if(mx>i) Len[i]=min(mx-i,Len[2*pos-i]); else Len[i]=1; while
(s[i-Len[i]]==s[i+Len[i]]) Len[i]++; if(Len[i]+i>mx) { mx=Len[i]+i; pos=i; } cnt=(cnt+Len[i]/2)%mo; } return cnt; } int main() { scanf("%s",&s); n=strlen(s); pw[0]=1; for(int i=1;i<=n;i++) pw[i]=pw[i-1]*2%mo; for(int i=0;i<n;i++) B[i]=s[i]=='a',C[n-i-1]=~B[i]; //C儲存反串的反,為了把同或變成異或 ans=pw[(B^C).count()+1>>1]-1; D=C; for(int i=1;i<n;i++) //左移方向 { C[n-i]=0; //高位變成零,因為兩頭是不參與運算的 ans=(ans+pw[(B>>i^C).count()+1>>1]-1)%mo; } for(int i=1;i<n;i++) //右移方向 { B[n-i]=0; ans=(ans+pw[(B^D>>i).count()+1>>1]-1)%mo; } tn=Mana_Init(s,n); printf("%d",(ans-Manacher(tmp,tn)+mo)%mo); return 0; }

 然而這份程式碼沒有過(卡時間卡得很緊呀,都不知道有幾個測試點)
 後來查閱了下題解,是用的FFT,並不是暴力。其實也很好理解。兩個字元關於一個軸對稱,那麼它們的位置總和必然是相同的。這和兩個二進位制來做一個高精度乘法(無進位)很相似。我們先讓a代表1,b代表0做一遍乘法,再讓a代表0,b代表1做一遍乘法,最後把相同位置的答案相加,得到的其實就是之前的CNT。
 FFT很生疏,當個模板題好了。這個模板用了庫中的complex類,所以效率不是很高,稍微改成自己的應該會快很多。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<bitset>
#include<complex>
#define mo 1000000007
using namespace std;

typedef complex<double> cp;

const double PI=3.14159265358979323846;

char s[100005],tmp[200005];
int Len[200005],n,tn,pw[100005],op[1<<18],rev[1<<18],ans,L,bit;
cp a[1<<18],b[1<<18];

void get_rev(int bit)
{
	for(int i=0;i<(1<<bit);i++)
		rev[i]=(rev[i>>1]>>1)|((i&1)<<bit-1);
}

void FFT(cp *a, int n, int dft)
{
	//++a;//if start from 1
	cp x,y;
	for(int i=0;i<n;i++)
		if(i<rev[i])
			swap(a[i],a[rev[i]]);
	for(int stp=1;stp<n;stp<<=1)
	{
		cp wn=exp(cp(0,dft*PI/stp));
		for(int j=0;j<n;j+=stp<<1)
		{
			cp wnk(1,0);
			for(int k=j;k<j+stp;k++)
			{
				x=a[k],y=wnk*a[k+stp];
				a[k]=x+y;
				a[k+stp]=x-y;
				wnk*=wn;
			}
		}
	}
	if(dft==-1)
		for(int i=0;i<n;i++)
			a[i]/=n;
}

int Mana_Init(char *s, int n)
{
	int i;
	tmp[0]='@';
	tmp[2*n+2]='$';
	tmp[2*n+1]='#';
	for(int i=1;i<=n*2;i+=2)
		tmp[i]='#',tmp[i+1]=s[i>>1];
	return 2*n+1;	
}

int Manacher(char *s, int n)
{
	int mx=0,cnt=0,pos=0;
	for(int i=1;i<=n;i++)
	{
		if(mx>i)
			Len[i]=min(mx-i,Len[2*pos-i]);
		else
			Len[i]=1;
		while(s[i-Len[i]]==s[i+Len[i]])
			Len[i]++;
		if(Len[i]+i>mx)
		{
			mx=Len[i]+i;
			pos=i;
		}
		cnt=(cnt+Len[i]/2)%mo;
	}
	return cnt;
}

int main()
{
	scanf("%s",&s);
	n=strlen(s);
	pw[0]=1;
	for(int i=1;i<=n;i++)
		pw[i]=pw[i-1]*2%mo;
	L=2;
	for(bit=1;(1<<bit)<2*n-1;bit++)
		L<<=1;
	get_rev(bit);
	for(int i=0;i<n;i++)
		a[i]=s[i]=='a';
	for(int i=0;i<n;i++)
		b[i]=s[i]=='a';
	FFT(a,L,1);
	FFT(b,L,1);
	for(int i=0;i<L;i++)
		a[i]*=b[i];
	FFT(a,L,-1);
	for(int i=0;i<L;i++)
		op[i]=a[i].real()+0.5;
	for(int i=n;i<L;i++)
		a[i]=b[i]=0;
	for(int i=0;i<n;i++)
		a[i]=s[i]=='b';
	for(int i=0;i<n;i++)
		b[i]=s[i]=='b';
	FFT(a,L,1);
	FFT(b,L,1);
	for(int i=0;i<L;i++)
		a[i]*=b[i];
	FFT(a,L,-1);
	for(int i=0;i<L;i++)
		op[i]+=a[i].real()+0.5;
	for(int i=0;i<L;i++)
		ans=(ans+pw[op[i]+1>>1]-1)%mo;
	tn=Mana_Init(s,n);
	printf("%d",(ans-Manacher(tmp,tn)+mo)%mo);
	return 0;
}

 最後吐槽一句bzoj居然不支援c++11…太落後了吧……