【FFT】【Manacher】【bitset】lydsy3160 萬徑人蹤滅
3160: 萬徑人蹤滅
Time Limit: 10 Sec Memory Limit: 256 MB
Submit: 2360 Solved: 1274
[Submit][Status][Discuss]
Description
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…太落後了吧……