【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…太落后了吧……

猜你喜欢

转载自blog.csdn.net/BUAA_Alchemist/article/details/84868791
今日推荐