打开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. 利用反射型XSS二次注入绕过CSP form-action限制

    利用反射型XSS二次注入绕过CSP form-action限制 翻译:SecurityToolkit 0x01 简单介绍 CSP(Content-Security-Policy)是为了缓解XSS而存在 ...

  2. ubuntu的PPA

    PPA,表示Personal Package Archives,也就是个人软件包集很多软件包由于各种原因吧,不能进入官方的Ubuntu软件仓库.为了方便Ubuntu用户使用,launchpad.net ...

  3. Linux MySQl 5.7.17 MySQL ERROR 1366(HY000):Incorrect string value 解决方法

    MySQL ERROR 1366(HY000):Incorrect string value,在往数据库中插入中文的时候会出现. 这也就是编码问题,网上大部分都是说设置下配置文件中的设置,而可悲的是在 ...

  4. MVC 视图页对数字,金额 用逗号 隔开(数字格式化)

    cshtml页面代码: <tr> <th>@Model.BankName</th> <th>@Model.Month</th> <th ...

  5. js如何查看元素类型

    <script type="text/javascript"> //定义变量temp var temp = Object.prototype.toString.appl ...

  6. 一张图教会CSS3倒影

    分享 示例图片 在CSS3之前,想要实现示例图片这样的一个倒影效果一般只能通过处理图片的方式,而CSS3问世之后,想要实现这样的效果变得非常简单,只需一个CSS3属性就可以轻松实现了. 这就是今天所要 ...

  7. ASP.NET MVC5 支持PUT 和DELETE

    Web.config <configuration> <system.webServer> <handlers> <remove name="Ext ...

  8. luoguP2735 电网 Electric Fences

    一道校内模拟赛遇见的题 ** 不会正解就真的很麻烦的 数学题 ** 有一种东西叫 皮克定理 发现的千古神犇: 姓名:George Alexander Pick(所以叫皮克定理呀 国籍:奥地利(蛤!竟然 ...

  9. tensorflow高级库

    1.tf.app.flags tf定义了tf.app.flags,用于支持接受命令行传递参数,相当于接受argv.tf.app.flags.DEFINE_xxx()就是添加命令行的optional a ...

  10. SQL CAST与CONVERT区别

    CAST 和 CONVERT 将某种数据类型的表达式显式转换为另一种数据类型.CAST 和 CONVERT 提供相似的功能. 语法 使用 CAST: CAST ( expression AS data ...