FFT
最近在学一个叫做FFT的东西(多项式我来啦
黑暗爆炸 - 4503
模板字符串匹配,卷一下就好啦
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=3e5+10;
const double pi=acos(-1.0);
ll n,m;
char ch[maxn];
struct comp
{
double x,y;
comp(double xx=0,double yy=0){x=xx,y=yy;}
}a[maxn],b[maxn],a2[maxn],b2[maxn],b3;
comp operator + (comp a,comp b){ return comp(a.x+b.x,a.y+b.y);}
comp operator - (comp a,comp b){ return comp(a.x-b.x,a.y-b.y);}
comp operator * (comp a,comp b){ return comp(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
int l=0,r[maxn];
int limit=1;
void FFT(comp a[],int type){
for(int i=0;i<limit;i++)
if(i<r[i])swap(a[i],a[r[i]]); //求出要迭代的序列
for(int mid=1;mid<limit;mid<<=1) //待合并区间的中点
{
comp wn(cos(pi/mid),type*sin(pi/mid)); //单位根
for(int R=mid<<1,j=0;j<limit;j+=R) //R是区间的右端点,j表示前已经到哪个位置了
{
comp w(1,0); //幂
for(int k=0;k<mid;k++,w=w*wn) //枚举左半部分
{
comp x=a[j+k],y=w*a[j+mid+k]; //蝴蝶变化
a[j+k]=x+y;
a[j+mid+k]=x-y;
}
}
}
}
vector<int>v;
int main()
{
scanf("%s",ch);
n=strlen(ch);
for(int i=0;i<n;i++)
if(ch[i]=='?')a[i].x=0;
else a[i].x=ch[i]-'a'+1;
scanf("%s",ch);
m=strlen(ch);
for(int i=0;i<m;i++)
if(ch[i]=='?')b[i].x=0;
else b[i].x=ch[i]-'a'+1;
reverse(b,b+m);
while(limit<=n+m)limit<<=1,l++;
for(int i=0;i<limit;i++)
a2[i]=a[i]*a[i],
b2[i]=(comp){2,0}*b[i]*b[i],b3.x+=b[i].x*b[i].x*b[i].x;
for(int i=0;i<limit;i++)
r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
FFT(a,1);FFT(b,1);FFT(a2,1);FFT(b2,1);
for(int i=0;i<limit;i++){
a2[i]=a2[i]*b[i]-a[i]*b2[i];
}
FFT(a2,-1);
for(int i=m-1;i<n;i++){
if((int)(a2[i].x/limit+b3.x+0.5)==0){
v.push_back(i-m+1);
}
}
printf("%d\n",v.size());
for(int i=0;i<v.size();i++){
printf("%d\n",v[i]);
}
return 0;
}
黑暗爆炸 - 4259
同上题,模板
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=7e5+10;
const double pi=acos(-1.0);
ll n,m;
char ch[maxn];
struct comp
{
double x,y;
comp(double xx=0,double yy=0){x=xx,y=yy;}
}a[maxn],b[maxn],a2[maxn],b2[maxn],b3[maxn],a3[maxn];
comp operator + (comp a,comp b){ return comp(a.x+b.x,a.y+b.y);}
comp operator - (comp a,comp b){ return comp(a.x-b.x,a.y-b.y);}
comp operator * (comp a,comp b){ return comp(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
int l=0,r[maxn];
int limit=1;
void FFT(comp a[],int type){
for(int i=0;i<limit;i++)
if(i<r[i])swap(a[i],a[r[i]]); //求出要迭代的序列
for(int mid=1;mid<limit;mid<<=1) //待合并区间的中点
{
comp wn(cos(pi/mid),type*sin(pi/mid)); //单位根
for(int R=mid<<1,j=0;j<limit;j+=R) //R是区间的右端点,j表示前已经到哪个位置了
{
comp w(1,0); //幂
for(int k=0;k<mid;k++,w=w*wn) //枚举左半部分
{
comp x=a[j+k],y=w*a[j+mid+k]; //蝴蝶变化
a[j+k]=x+y;
a[j+mid+k]=x-y;
}
}
}
}
vector<int>v;
int main()
{
scanf("%d%d",&n,&m);
scanf("%s",ch);
for(int i=0;i<n;i++)
if(ch[i]=='*')a[i].x=0;
else a[i].x=ch[i]-'a'+1;
scanf("%s",ch);
m=strlen(ch);
for(int i=0;i<m;i++)
if(ch[i]=='*')b[i].x=0;
else b[i].x=ch[i]-'a'+1;
reverse(a,a+n);
while(limit<=n+m)limit<<=1,l++;
for(int i=0;i<limit;i++)
a2[i]=a[i]*a[i],
b2[i]=(comp){2,0}*b[i]*b[i],
a3[i]=a[i]*a[i]*a[i],
b3[i]=b[i]*b[i]*b[i];
for(int i=0;i<limit;i++)
r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
FFT(a3,1);FFT(b3,1);FFT(a2,1);FFT(b2,1);
FFT(a,1);FFT(b,1);
for(int i=0;i<limit;i++){
a2[i]=a3[i]*b[i]-a2[i]*b2[i]+b3[i]*a[i];
}
FFT(a2,-1);
for(int i=n-1;i<m;i++){
if((int)(a2[i].x/limit+0.5)==0){
v.push_back(i-n+1);
}
}
printf("%d\n",v.size());
for(int i=0;i<v.size();i++){
printf("%d ",v[i]+1);
}
return 0;
}
CodeForces - 528D
模糊字符串匹配,我太菜了,看了题解。
原来的想法,是预处理出A串中能匹配的字符,然后只要和B串卷积连乘就行了,比如式子是这样的:
但是这个式子展开也太离谱了!
题解的思路:
因为字符集只有’A,C,G,T’,我们把每个字符分开做贡献,如果看从一个位置开始能匹配几个给定字符,然后再把所有的字符累加即可,如果一项的系数是B串的长度,即意味着匹配成功。
对于一个字符c,
如果A串的第i个位置可以放c,那么 f i = 1 fi=1 fi=1,否则 f i = 0 fi=0 fi=0。
如果B串的第i个位置可以放c,那么 g i = 1 gi=1 gi=1,否则 g i = 0 gi=0 gi=0。
然后卷积:
h i = ∑ f j hi=∑fj hi=∑fj g i − j gi−j gi−j
然后把4个字符的解累加起来。
r e s i = ∑ h [ c ] resi=∑h[c] resi=∑h[c]
显然,如果 r e s [ i ] = ∣ B ∣ res [i]=|B| res[i]=∣B∣,那么从A串的第 i − ∣ B ∣ + 1 i−|B|+1 i−∣B∣+1位开始可以匹配。
然后时间复杂度还是O( ( ∣ A ∣ + ∣ B ∣ ) l o g ( ∣ A ∣ + ∣ B ∣ ) (|A|+|B|)log(|A|+|B|) (∣A∣+∣B∣)log(∣A∣+∣B∣))。常数小了很多,也不用自己展开了。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=7e5+10;
const double pi=acos(-1.0);
ll n,m,k;
struct comp
{
double x,y;
comp(double xx=0,double yy=0){x=xx,y=yy;}
}x[maxn],y[maxn],z[maxn];
comp operator + (comp a,comp b){ return comp(a.x+b.x,a.y+b.y);}
comp operator - (comp a,comp b){ return comp(a.x-b.x,a.y-b.y);}
comp operator * (comp a,comp b){ return comp(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
int l=0,r[maxn];
int limit=1;
void FFT(comp a[],int type){
for(int i=0;i<limit;i++)
if(i<r[i])swap(a[i],a[r[i]]); //求出要迭代的序列
for(int mid=1;mid<limit;mid<<=1) //待合并区间的中点
{
comp wn(cos(pi/mid),type*sin(pi/mid)); //单位根
for(int R=mid<<1,j=0;j<limit;j+=R) //R是区间的右端点,j表示前已经到哪个位置了
{
comp w(1,0); //幂
for(int k=0;k<mid;k++,w=w*wn) //枚举左半部分
{
comp x=a[j+k],y=w*a[j+mid+k]; //蝴蝶变化
a[j+k]=x+y;
a[j+mid+k]=x-y;
}
}
}
}
int mp[300],a[maxn],b[maxn];
int res[maxn];
char ch[maxn];
int main()
{
mp['A']=0;mp['C']=1;mp['G']=2;mp['T']=3;
scanf("%d%d%d",&n,&m,&k);
scanf("%s",ch);
for(int i=0;i<n;i++){
a[i]=mp[ch[i]];
}
scanf("%s",ch);
for(int i=0;i<m;i++){
b[i]=mp[ch[i]];
}
reverse(b,b+m);
while(limit<=n+m)limit<<=1,l++;
for(int i=0;i<limit;i++)
r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
memset(res,0,sizeof res);
for(int i=0;i<4;i++){
for(int j=0;j<limit;j++){
x[j].x=y[j].x=0;
x[j].y=y[j].y=0;
}
int cnt=0;
for(int j=0;j<k;j++)
if(a[j]==i)cnt++;
for(int j=0;j<n;j++){
if(j+k<n)
if(a[j+k]==i)cnt++;
if(j-k-1>=0)
if(a[j-k-1]==i)cnt--;
if(cnt>0)x[j].x=1;
}
for(int j=0;j<m;j++){
if(b[j]==i)y[j].x=1;
}
FFT(x,1);FFT(y,1);
for(int j=0;j<limit;j++){
z[j]=x[j]*y[j];
}
FFT(z,-1);
for(int j=0;j<n;j++){
res[j]+=(int(z[j].x/limit+0.5));
}
}
int ans=0;
for(int i=m-1;i<n;i++)
if(res[i]==m)ans++;
printf("%d\n",ans);
return 0;
}