打开51Nod全部问题页面,在右边题目分类中找到快速傅里叶变换,然后按分值排序,就是本文的题目顺序。

1.大数乘法问题

这个……板子就算了吧。

2.美妙的序列问题

长度为n的排列,且满足从中间任意位置划分为两个非空数列后,左边的最大值>右边的最小值。问这样的排列有多少个%998244353。

多组询问,n,T<=100000。

题解:经过分析可知,不合法的排列一定存在这样一种划分:

我们考虑答案=f[i]=i!-不合法排列个数。

形如 2 1 3 4 6 5 这种排列,会有三种划分方式不合法(1 | 3,3 | 4,4 | 6),直接算阶乘会计算重复。

而我们又发现,后两种划分,左边的子串仍是一个不合法的排列(显然)。

于是我们强制要求左边的排列是一个合法的排列,即在最左边统计贡献,这样就可以不重不漏了。

得到递推式显然:

分治NTT即可,预处理后O(1)回答。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <map>
#include <set>
#define LL long long
#define FILE "美妙的序列"
using namespace std; const int N = ;
const int Mod = ;
const int G = ;
int f[N],rev[N],L,Jc[N],a[N],b[N]; inline int gi(){
int x=,res=;char ch=getchar();
while(ch>'' || ch<'')res^=ch=='-',ch=getchar();
while(ch>=''&&ch<='')x=x*+ch-,ch=getchar();
return res?x:-x;
} inline int QPow(int d,int z,int ans=){
for(;z;z>>=,d=1ll*d*d%Mod)
if(z&)ans=1ll*ans*d%Mod;
return ans;
} inline void NTT(int *A,int n,int f){
for(int i=;i<n;++i)
if(i<rev[i])swap(A[i],A[rev[i]]);
for(int i=;i<n;i<<=){
int z=f*(Mod-)/(i<<),gn=QPow(G,(z+Mod-)%(Mod-));
for(int j=;j<n;j+=i<<){
int g=,x,y;
for(int k=;k<i;++k,g=1ll*g*gn%Mod){
x=A[j+k];y=1ll*g*A[i+j+k]%Mod;
A[j+k]=(x+y)%Mod;A[i+j+k]=(x-y+Mod)%Mod;
}
}
}
if(f==)return;int iv=QPow(n,Mod-);
for(int i=;i<n;++i)A[i]=1ll*A[i]*iv%Mod;
} inline void solve(int l,int r){
if(l==r){f[l]=(Jc[l]-f[l]%Mod+Mod)%Mod;return;}
int mid=(l+r)>>;
solve(l,mid);
int n,m=r-l+;L=;
for(n=;n<=m;n<<=)L++;
for(int i=;i<n;++i)
rev[i]=(rev[i/]/)|((i&)<<(L-)); for(int i=;i<n;++i)a[i]=b[i]=;
for(int i=l;i<=mid;++i)a[i-l]=f[i];
for(int i=;i<m;++i)b[i]=Jc[i];
NTT(a,n,);NTT(b,n,);
for(int i=;i<n;++i)a[i]=1ll*a[i]*b[i]%Mod;
NTT(a,n,-);
for(int i=mid+;i<=r;++i)f[i]=(f[i]+a[i-l])%Mod;
solve(mid+,r);
} int main(){
//freopen(FILE".in","r",stdin);
//freopen(FILE".out","w",stdout);
int Case=gi();Jc[]=;
for(int i=;i<=;++i)
Jc[i]=1ll*Jc[i-]*i%Mod;
solve(,);
while(Case--)printf("%d\n",f[gi()]);
fclose(stdin);fclose(stdout);
return ;
}

美妙的序列

3.哈希统计问题

给定base,p,求经过经典哈希(ans=(ans*base+a[i])%p;)后哈希值=x的长度<=n的小写字符串个数%998244353,n,p,base<=50000。

题解:对于长度<=n的问题先不考虑,先考虑恰好为n的。

设f[i][j]为已有i个字母,哈希值为j的串个数,则转移为: f[i][j] -> f[i+1][(j*base+Ascll[c])%p]。

如果把j*base看成模p意义下的j',显然转移是一个多项式相乘形式。

常见的套路是:观察当i为偶数时,f[i/2]是否能直接推出f[i]。

显然可以,j*base变成j*basei/2就可以了。写出来是一个卷积的形式,一遍NTT即可。

于是直接暴力递归,i为奇数则化为f[i-1]*f[1]继续暴力,只会做O(log)次。

现在要求<=n的,那么同样设pre[i][j]为已有<=i个字母,哈希值为j的串的个数。

转移:pre[i]*f[j]+pre[j] -> pre[i+j]。

即:选[j+1,i+j]个的和选[1,j]的方案数相加,就是选[1,i+j]个的个数。

剩下的就是一点细节,调试一会儿应该也很好写出来。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <map>
#include <set>
#define LL long long
#define FILE "哈希统计"
using namespace std; const int N = ;
const int M = ;
const int Mod = ;
const int G = ;
int p,Bs,m,wx;
int idf,idpre,f[M][N],pre[M][N],f_vis[N],pre_vis[N];
int n,rev[N],L,a[N],b[N]; inline int gi(){
int x=,res=;char ch=getchar();
while(ch>'' || ch<'')res^=ch=='-',ch=getchar();
while(ch>=''&&ch<='')x=x*+ch-,ch=getchar();
return res?x:-x;
} inline int QPow(int d,int z,int Mod,int ans=){
for(;z;z>>=,d=1ll*d*d%Mod)
if(z&)ans=1ll*ans*d%Mod;
return ans;
} inline void NTT(int *A,int f){
for(int i=;i<n;++i)
if(i<rev[i])swap(A[i],A[rev[i]]);
for(int i=;i<n;i<<=){
int z=f*(Mod-)/(i<<),gn=QPow(G,(z+Mod-)%(Mod-),Mod);
for(int j=;j<n;j+=i<<){
int g=,x,y;
for(int k=;k<i;++k,g=1ll*g*gn%Mod){
x=A[j+k];y=1ll*g*A[i+j+k]%Mod;
A[j+k]=(x+y)%Mod;A[i+j+k]=(x-y+Mod)%Mod;
}
}
}
if(f==)return;int iv=QPow(n,Mod-,Mod);
for(int i=;i<n;++i)A[i]=1ll*A[i]*iv%Mod;
} inline void Mul(int *H,int *g,int *h){
NTT(g,);NTT(h,);
for(int i=;i<n;++i)
H[i]=1ll*g[i]*h[i]%Mod;
NTT(H,-);
for(int i=n-;i>=p;--i)
H[i-p]=(H[i-p]+H[i])%Mod,H[i]=;
} inline int getf(int x){
if(f_vis[x])return f_vis[x];
if(x==){
++idf;
for(int i='a';i<='z';++i)
f[idf][i%p]++;
return idf;
}
int id0,id1,len0,len1,pw;
if(x&)id0=getf(len0=x-),id1=getf(len1=);
else id0=id1=getf(len0=len1=x/);
++idf;pw=QPow(Bs,len1,p); for(int i=;i<n;++i)a[i]=,b[i]=f[id1][i];
for(int i=;i<p;++i)
if(f[id0][i]){
int y=1ll*i*pw%p;
a[y]=(a[y]+f[id0][i])%Mod;
}
Mul(f[idf],a,b);
return f_vis[x]=idf;
} inline int getpre(int x){
if(pre_vis[x])return pre_vis[x];
if(x==){
++idpre;int id=getf(x);
for(int i=;i<n;++i)
pre[idpre][i]=f[id][i];
return idpre;
}
int id0,id1,id2,len0,len1,pw;
if(x&)id0=getpre(len0=x-),id1=getf(len1=);
else id0=getpre(len0=x/),id1=getf(len1=x/);
id2=getpre(len1);pw=QPow(Bs,len1,p);++idpre; for(int i=;i<n;++i)a[i]=,b[i]=f[id1][i];
for(int i=;i<p;++i)
if(pre[id0][i]){
int y=1ll*i*pw%p;
a[y]=(a[y]+pre[id0][i])%Mod;
}
Mul(pre[idpre],a,b);
for(int i=;i<p;++i)
pre[idpre][i]=(pre[idpre][i]+pre[id2][i])%Mod;
return pre_vis[x]=idpre;
} int main(){
//freopen(FILE".in","r",stdin);
//freopen(FILE".out","w",stdout);
m=gi();Bs=gi();p=gi();wx=gi();
for(n=;n<p+p;n<<=)L++;
for(int i=;i<n;++i)
rev[i]=(rev[i/]/)|((i&)<<(L-));
int id=getpre(m);
printf("%d\n",pre[id][wx]);
fclose(stdin);fclose(stdout);
return ;
}

哈希统计

4.乘积之和

给定正整数序列序列A[1...n],有Q次询问,每次询问给出k,在A中任选k个数可以得到一个乘积。求所有方案的乘积的总和%100003。n,Q<=50000。

题解:暴力DP很显然,设f[i][j]表示前i个数选j个数的乘积和,那么可以直接转移f[i][j] -> f[i+1][j*A[i+1]%100003]。

用上面那题的套路,f[i/2]是否能推出f[i]?仔细分析后发现是可以的。

发现这也是一个卷积形式!但是这题是有多组询问的,不能直接暴力递归求。

分治•NTT,solve(l,r)表示得到在A[l...r]中选k个的乘积和数组,总复杂度O(nlog2n),最后求出所有解。

因为不是费马质数,卷积上界又只有10^14级别,两个费马质数用中国剩余定理合并一下就可以了。

upd:用母函数来理解可能更好。

易推出我们求的是多项式

在xk项的系数。同样的,上式可用分治法,合并时使用NTT,代码是一样的。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <map>
#include <set>
#define LL long long
#define FILE "乘积之和"
using namespace std; const LL N = ;
const LL M = ;
const LL G = ;
LL Q,A[N];
LL f[][N],rev[N];
LL P[]={,}; inline LL gi(){
LL x=,res=;char ch=getchar();
while(ch>'' || ch<'')res^=ch=='-',ch=getchar();
while(ch>=''&&ch<='')x=x*+ch-,ch=getchar();
return res?x:-x;
} inline LL Mul(LL a,LL b,LL Mod,LL ans=){
if(Mod<=P[])return a*b%Mod;
for(;b;b>>=,a=(a+a)%Mod)
if(b&)ans=(ans+a)%Mod;
return ans;
} inline LL QPow(LL d,LL z,LL Mod,LL ans=){
for(d=d%Mod,z=z%Mod;z;z>>=,d=d*d%Mod)
if(z&)ans=ans*d%Mod;
return ans;
} inline void NTT(LL *A,LL n,LL f,LL Mod){
for(LL i=;i<n;++i)
if(i<rev[i])swap(A[i],A[rev[i]]);
for(LL i=;i<n;i<<=){
LL z=f*(Mod-)/(i<<),gn=QPow(G,(z+Mod-)%(Mod-),Mod);
for(LL j=;j<n;j+=i<<){
LL g=,x,y;
for(LL k=;k<i;k++,g=1ll*g*gn%Mod){
x=A[j+k];y=1ll*g*A[i+j+k]%Mod;
A[j+k]=(x+y)%Mod;A[i+j+k]=(x-y+Mod)%Mod;
}
}
}
if(f==)return;LL iv=QPow(n,Mod-,Mod);
for(LL i=;i<n;++i)A[i]=1ll*A[i]*iv%Mod;
} inline LL CRT(LL r0,LL r1){
LL Mod=1ll*P[]*P[];
LL v0=QPow(P[],P[]-,P[]),v1=QPow(P[],P[]-,P[]);
LL r=(Mul(v0*P[]%Mod,r0,Mod)+Mul(v1*P[]%Mod,r1,Mod))%Mod;
return r%M;
} inline void solve(LL l,LL r,LL dep){
if(l==r){
f[dep][]=;f[dep][]=A[l]%M;
return;
}
LL mid=(l+r)>>;
LL m=r-l+,n=,L=;
for(;n<=m;n<<=)L++; LL a[][n+],b[][n+]; solve(l,mid,dep+);
for(LL i=;i<=mid-l+;++i)a[][i]=a[][i]=f[dep+][i];
for(LL i=mid-l+;i<n;++i)a[][i]=a[][i]=; solve(mid+,r,dep+);
for(LL i=;i<=r-mid;++i)b[][i]=b[][i]=f[dep+][i];
for(LL i=r-mid+;i<n;++i)b[][i]=b[][i]=; for(LL i=;i<n;++i)
rev[i]=(rev[i/]/)|((i&)<<(L-)); for(LL t=;t<;++t){
NTT(a[t],n,,P[t]);NTT(b[t],n,,P[t]);
for(LL i=;i<n;++i)a[t][i]=1ll*a[t][i]*b[t][i]%P[t];
NTT(a[t],n,-,P[t]);
} for(LL i=;i<=m;++i)
f[dep][i]=CRT(a[][i],a[][i]); } int main(){
LL n=gi();Q=gi();
for(LL i=;i<=n;++i)A[i]=gi();
solve(,n,);
for(LL t=;t<=Q;++t)
printf("%lld\n",f[][gi()]);
fclose(stdin);fclose(stdout);
return ;
}

乘积之和

5.模糊搜索问题

题意:给定两个串A,B,字符集大小为4,匹配规则是若A[j-k]~A[j+k]中存在B[i]则算B[i]在A[j]出现,求B在A中出现了多少次,长度<=100000。

比如说k=2的情况。

题解:这种字符串问题用FFT来做的套路似乎都和万径人踪灭差不多?

首先那个k的限制可以用两遍扫+差分搞定出字符c在A[i]出是否算的上出现,记为g[c][i]。

B在A[i]处开头,则对于'A'、'T'、'C'、‘G’,,有如下式子:

这样仍不好做,把式子构造一下:

这就形成了多项式乘法的形式,跑四遍就可以了。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <map>
#include <set>
#define LL long long
#define FILE "模糊搜索"
using namespace std; const int N = ;
const double pi = acos(-1.0);
int S,T,K,n,m,L,rev[N],ID[],cf[N],num[][N],Ans;
struct dob{
double real,imag;
dob(){};
dob(double _r,double _i){real=_r;imag=_i;}
dob operator +(const dob &a)const{
return (dob){real+a.real,imag+a.imag};
}
dob operator -(const dob &a)const{
return (dob){real-a.real,imag-a.imag};
}
dob operator *(const dob &a)const{
double r=real*a.real-imag*a.imag;
double i=real*a.imag+imag*a.real;
return (dob){r,i};
}
}a[N],b[N],f[][N];
char s[N],t[N]; inline int gi(){
int x=,res=;char ch=getchar();
while(ch>''||ch<''){if(ch=='-')res*=-;ch=getchar();}
while(ch<=''&&ch>='')x=x*+ch-,ch=getchar();
return x*res;
} inline void FFT(dob *A,int f){
for(int i=;i<n;++i)
if(i<rev[i])swap(A[i],A[rev[i]]);
for(int i=;i<n;i<<=){
dob wn(cos(pi/i),sin(f*pi/i)),x,y;
for(int j=;j<n;j+=i<<){
dob w(,);
for(int k=;k<i;k++,w=w*wn){
x=A[j+k];y=w*A[i+j+k];
A[j+k]=x+y;A[i+j+k]=x-y;
}
}
}
if(f==)return;
for(int i=;i<n;++i)
A[i].real=int(A[i].real/n+0.5);
} inline void work(char ch,int sum=){
for(int i=;i<n;++i)cf[i]=;
for(int i=;i<=S;++i)
if(s[i]==ch){
cf[max(,i-K)]++;
cf[min(n+,i+K+)]--;
}
for(int i=;i<n;++i)cf[i]+=cf[i-];
for(int i=;i<=S;++i)
a[i].real=cf[i]>,a[i].imag=;
for(int i=S+;i<n;++i)
a[i].real=a[i].imag=;
for(int i=;i<=T;++i)
sum+=b[i].real=t[i]==ch,b[i].imag=;
for(int i=T+;i<n;++i)
b[i].real=b[i].imag=;
reverse(b+,b+T+);
FFT(a,);FFT(b,);
for(int i=,id=ID[ch];i<n;++i)
f[id][i]=a[i]*b[i];
FFT(f[ID[ch]],-);
for(int i=;i<=S;++i)
num[ID[ch]][i]=sum==int(f[ID[ch]][i+T].real+0.001);
} int main(){
//freopen(FILE".in","r",stdin);
//freopen(FILE".out","w",stdout);
S=gi();T=gi();K=gi();
for(n=,m=S+T;n<m;n<<=)L++;
for(int i=;i<n;++i)
rev[i]=(rev[i/]/)|((i&)<<(L-));
ID['A']=;ID['T']=;ID['C']=;ID['G']=;
scanf("%s%s",s+,t+);
work('A');work('T');work('C');work('G');
for(int i=;i<=S;++i)
if(num[][i] && num[][i] && num[][i] && num[][i])
Ans++;
printf("%d\n",Ans);
fclose(stdin);fclose(stdout);
return ;
}

模糊搜索问题

51Nod 快速傅里叶变换题集选刷的更多相关文章

  1. 51nod 贪心算法题集

    2070 最小罚款: 题意:初始有n元,每个任务有2个参数:t和w,<=t时刻前完成任务才可避免造成损失w.问:如何安排才能尽可能避免损失?一个任务执行时间是一个单位时间. 分析:任务按时间排个 ...

  2. 多项式 之 快速傅里叶变换(FFT)/数论变换(NTT)/常用套路【入门】

    原文链接https://www.cnblogs.com/zhouzhendong/p/Fast-Fourier-Transform.html 多项式 之 快速傅里叶变换(FFT)/数论变换(NTT)/ ...

  3. 快速傅里叶变换(FFT)学习笔记(未完待续)

    目录 参考资料 FFT 吹水 例题 普通做法 更高大尚的做法 定义与一部分性质 系数表达式 点值表达式 点值相乘??? 卷积 复数 单位根 DFT IDFT 蝴蝶迭代优化 单位根求法 实现.细节与小优 ...

  4. 【笔记篇】(理论向)快速傅里叶变换(FFT)学习笔记w

    现在真是一碰电脑就很颓废啊... 于是早晨把电脑锁上然后在旁边啃了一节课多的算导, 把FFT的基本原理整明白了.. 但是我并不觉得自己能讲明白... Fast Fourier Transformati ...

  5. Algorithm: 多项式乘法 Polynomial Multiplication: 快速傅里叶变换 FFT / 快速数论变换 NTT

    Intro: 本篇博客将会从朴素乘法讲起,经过分治乘法,到达FFT和NTT 旨在能够让读者(也让自己)充分理解其思想 模板题入口:洛谷 P3803 [模板]多项式乘法(FFT) 朴素乘法 约定:两个多 ...

  6. 【数学】快速傅里叶变换(FFT)

    快速傅里叶变换(FFT) FFT 是之前学的,现在过了比较久的时间,终于打算在回顾的时候系统地整理一篇笔记,有写错的部分请指出来啊 qwq. 卷积 卷积.旋积或褶积(英语:Convolution)是通 ...

  7. 浅谈FFT(快速傅里叶变换)

    前言 啊摸鱼真爽哈哈哈哈哈哈 这个假期努力多更几篇( 理解本算法需对一些< 常 用 >数学概念比较清楚,如复数.虚数.三角函数等(不会的自己查去(其实就是懒得写了(¬︿̫̿¬☆) 整理了一 ...

  8. [学习笔记] 多项式与快速傅里叶变换(FFT)基础

    引入 可能有不少OIer都知道FFT这个神奇的算法, 通过一系列玄学的变化就可以在 $O(nlog(n))$ 的总时间复杂度内计算出两个向量的卷积, 而代码量却非常小. 博主一年半前曾经因COGS的一 ...

  9. 快速傅里叶变换FFT& 数论变换NTT

    相关知识 时间域上的函数f(t)经过傅里叶变换(Fourier Transform)变成频率域上的F(w),也就是用一些不同频率正弦曲线的加 权叠加得到时间域上的信号. \[ F(\omega)=\m ...

随机推荐

  1. HaoZipC不是内部或外部命令

    Win7专业版,32位,HaoZip V3.2 将安装命令下HaoZipC.exe,HaoZip.dll,lang\HaoZipLang_chs.dll三个文件拷贝到C:\Windows\System ...

  2. python基础===一行 Python 代码实现并行(转)

    原文:https://medium.com/building-things-on-the-internet/40e9b2b36148 译文:https://segmentfault.com/a/119 ...

  3. 数据结构之线性表(python版)

    数据结构之线性表(python版) 单链表 1.1  定义表节点 # 定义表节点 class LNode(): def __init__(self,elem,next = None): self.el ...

  4. Python的set集合详解

    Python 还包含了一个数据类型 -- set (集合).集合是一个无序不重复元素的集.基本功能包括关系测试和消除重复元素.集合对象还支持 union(联合),intersection(交),dif ...

  5. nginx php mysql日志配置

    1.编辑mysql的配置文件my.cnf,这个文件通常在/etc目录下,但我用rpm装mysql的时候这个配置文件是在/usr目录下,但我测试过,无论是放在/etc目录下,还是放在     /usr目 ...

  6. MyBatis3-实现MyBatis分页

    此文章中的例子是沿用上一篇文章http://www.cnblogs.com/EasonJim/p/7055499.html的Spring MVC集成的例子改装的. MyBatis分页有以下方式实现: ...

  7. DevExpress CxGrid 隐藏 Drag a column header to group by that column

  8. GreenPlum学习笔记:基础知识

    一.介绍 GreenPlum分布式数据仓库,大规模并行计算技术. 无共享/MPP核心架构 Greenplum数据库软件将数据平均分布到系统的所有节点服务器上,所以节点存储每张表或表分区的部分行,所有数 ...

  9. MySQL学习笔记:insert into select

    从一个表复制数据插入到另外一个表,目标表中任何已存在的行都不会受影响. 语法: INSERT INTO table_xxx VALUES(); INSERT INTO table_xxx SELECT ...

  10. 20165203 实验三 敏捷开发与XP实践

    20165203 实验三 敏捷开发与XP实践 任务一: 1.实验要求 实验三 敏捷开发与XP实践 (http://www.cnblogs.com/rocedu/p/4795776.html), Ecl ...