2021.10.8
T1:字符串匹配
Problem:
你正在玩galgame,你的母上大人开门进来,于是你假装开始做SB字符串匹配题:给定两个个字符串,但是这个字符串,它不给劲,有的地方残缺了,但是你不管那么多,就摁匹配,在你眼中残缺的部分可以视为任何字符。那么问题来了,对于给定的两个字符串A,B,输出A在B中出现的次数及位置(首字母),若未出现则输出0。
Solution:
关于FFT跑字符串匹配:
-
普通单模单匹
问题概述:给定模式串A(长度为m)、文本串B(长度为n),需要求出所有位置p,满足B串从第p个字符开始的连续m个字符,与A串完全相同
为了使问题更便于研究,我们约定所有字符串的下标从0开始
我们定义字符串的匹配函数 C(x,y) = A(x) - B(y) 那么我们可以形式化地定义“匹配”:若 C(x,y) = 0 ,则称A的第x个字符和B的第y个字符匹配。再定义完全匹配函数 P(x) = Σ(i=0~m-1) C(i,x-m+i+1),若P(x)=0,则称B以第x位结束的连续m位,与A完全匹配
但有没有觉得这个完全匹配函数有什么问题?似乎,根据完全匹配函数,“ab”与“ba”是匹配的?问题出在我们定义的匹配函数身上。匹配函数的确可以判断某一位是否匹配,但加了一个求和符号,一切都变了,只要两个串所含的字符是一样的,不管排列如何,完全匹配函数都会返回0值。
而这一切的罪魁祸首就是匹配函数的正负号!我们现在要做的是:定义一个更好的匹配函数,只要两个字符串某一位的匹配函数非零,完全匹配函数也不可能为零。不难想到给匹配函数加一个绝对值符号。但这样似乎就只能 O(nm) 暴力计算,没有更好的优化方法了。于是我们给匹配函数加上一个平方符号。此时完全匹配函数就是P(x) = Σ(i=0~m-1) [A(i) - B(x-m+i+1)]2
这样还是没什么优化方法。我们不妨翻转A串,将翻转后的结果设为S。形式化地,S(x) = A(m-x-1)。据此不难推出 A(x) = S(m-x-1)。于是完全匹配函数可以写成
P(x) = Σ(i=0~m-1) [S(m-i-1) - B(x-m+i+1)]2
大力展开这个函数:
P(x) = Σ(i=0~m-1) [S(m-i+1)2 + B(x-m+i+1)2 - 2 * S(m-i-1) * B(x-m+i+1)]
再将求和符号拆开:
P(x) = Σ(i=0~m-1) S(m-i-1)2 + Σ(i=0~m-1) B(x-m+i+1)2 - 2 Σ(i=0~m-1) S(m-i-1) B(x-m+i+1)
第一项是一个定值,可以O(m)预处理出来。第二项可以O(n)预处理前缀和。现在问题就在第三项。第三项中,两个小括号里面的东西加起来,似乎能抵消很多东西?居然……刚好等于x?这是巧合吗?所以,第三项是不是可以写成 -2 Σ(i+j=x) S(i) B(j) 呢?当然可以!
那么,我们设:T = Σ(i=0~m-1) S(i)2
f(x) = Σ (i=0~x) B(i)2
g(x) = Σ(i+j=x) S(i) B(j)
于是就有
P(x) = T + f(x) - f(x-m) - 2g(x)
观察这个g函数,发现它不就是我们熟悉的卷积形式吗?而且还是最常规的卷积——多项式乘法!于是可以用FFT计算出g函数了!然后再代入计算一下,就可以O(n)得到所有P(x)的值了!
-
带通配符的单模式串匹配
问题概述:给定模式串A(长度为m)、文本串B(长度为n),串的某些字符是“通配符”(通配符是一种可以与任意字符匹配的字符)。需要求出所有位置p,满足B串从第p个字符开始的连续m个字符,与A串完全相同
-
定义匹配函数
-
定义完全匹配函数
-
快速计算每一位的完全匹配函数值
有了通配符,我们肯定需要重新定义一个匹配函数
我们设通配符的数值为0,定义匹配函数 C(x,y) = [A(x) - B(y)]2 A(x) B(y) 这样就完美地解决了通配符匹配问题。那么我们的完全匹配函数就是
P(x) = Σ(i=0~m-1) [A(i) - B(x-m+i+1)]2 A(i) B(x-m+i+1)
还是老套路,将A串翻转,也就是设 S(i) = A(m-i-1)
然后完全匹配函数变成:
P(x) = Σ(i=0~m-1) [S(m-i-1) - B(x-m+i+1)]2 S(m-i-1) B(x-m+i+1)
暴力展开:
P(x) = Σ(i=0~m-1) S(m-i-1)3 B(x-m+i+1) + Σ(i=0~m-1) S(m-i-1) B(x-m+i+1)3 -2 Σ(i=0~m-1) S(m-i-1)2 B(x-m+i+1)2
发现所有的项,都满足两个小括号加起来等于x,所以全部写成卷积的形式:
P(x) = Σ(i+j=x) S(i)3 B(j) + Σ(i+j=x) S(i) B(j)3 - 2 Σ(i+j=x) S(i)2 B(j)2
这样只要做6次FFT外加1次IFFT就可以得到完全匹配函数每一位的值了
Code:
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=2000005; 4 const double Pi=acos(-1.0); 5 struct Complex{ 6 double x,y; 7 friend Complex operator + (const Complex &a,const Complex &b){ 8 return ((Complex){a.x+b.x,a.y+b.y}); 9 } 10 friend Complex operator - (const Complex &a,const Complex &b){ 11 return ((Complex){a.x-b.x,a.y-b.y}); 12 } 13 friend Complex operator * (const Complex &a,const Complex &b){ 14 return ((Complex){a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x}); 15 } 16 friend Complex operator * (const Complex &a,const double &val){ 17 return ((Complex){a.x*val,a.y*val}); 18 } 19 }f[Maxn],g[maxn],p[maxn]; 20 int n,m,lim=1,mx,rev[maxn],a[maxn],b[maxn]; 21 char S[maxn],T[maxn]; 22 bool used[maxn]; 23 vector<int>v; 24 inline void FFT(Complex *A,int opt){ 25 for(int i=0;i<lim;i++) if(i<rev[i]) swap(A[i],A[rev[i]]); 26 for(int mid=1;mid<lim;mid<<=1){ 27 Complex Wn=((Complex){cos(Pi/(double)mid),(double)opt*sin(Pi/(double)mid)}); 28 for(int j=0;j<lim;j+=(mid<<1)){ 29 Complex W=((Complex){1,0}); 30 for(int k=0;k<mid;k++,W=W*Wn){ 31 Complex x=A[j+k],y=W*A[j+k+mid]; 32 A[j+k]=x+y; 33 A[j+k+mid]=x-y; 34 } 35 } 36 } 37 } 38 signed main(){ 39 scanf("%d%d",&m,&n); 40 scanf("%s",T+1); 41 scanf("%s",S+1); 42 for(int i=1;i<=m;i++) if(T[i]!='*') a[i-1]=T[i]-'a'+1; 43 for(int i=1;i<=n;i++) if(S[i]!='*') b[i-1]=S[i]-'a'+1; 44 while(lim<=(n+m)){ 45 lim<<=1; 46 mx++; 47 } 48 for(int i=0;i<lim;i++) rev[i]=((rev[i>>1]>>1)|((i&1)<<mx-1)); 49 reverse(a,a+m); 50 for(int i=0;i<m;i++) f[i]=((Complex){a[i]*a[i]*a[i],0}); 51 for(int i=0;i<n;i++) g[i]=((Complex){b[i],0}); 52 FFT(f,1); 53 FFT(g,1); 54 for(int i=0;i<lim;i++) p[i]=p[i]+f[i]*g[i]; 55 for(int i=0;i<lim;i++) f[i]=g[i]=((Complex){0,0}); 56 for(int i=0;i<m;i++) f[i]=((Complex){a[i]*a[i],0}); 57 for(int i=0;i<n;i++) g[i]=((Complex){b[i]*b[i],0}); 58 FFT(f,1); 59 FFT(g,1); 60 for(int i=0;i<lim;i++) p[i]=p[i]-f[i]*g[i]*2.0; 61 for(int i=0;i<lim;i++) f[i]=g[i]=((Complex){0,0}); 62 for(int i=0;i<m;i++) f[i]=((Complex){a[i],0}); 63 for(int i=0;i<n;i++) g[i]=((Complex){b[i]*b[i]*b[i],0}); 64 FFT(f,1); 65 FFT(g,1); 66 for(int i=0;i<lim;i++) p[i]=p[i]+f[i]*g[i]; 67 FFT(p,-1); 68 for(int i=m-1;i<n;i++) if(!(int)(p[i].x/(double)lim+0.5)) v.push_back(i-m+2); 69 int Ans=v.size(); 70 printf("%d\n",Ans); 71 for(int i=0;i<Ans;i++) printf("%d ",v[i]); 72 return 0; 73 }
T2:矩阵反演
Problem:
Solution:
这道题一看a,b在指数上,肯定不能模拟=。=
例如a=2,b=1时有
0 1 2 3 4 5 6 7 0 4 1 5 2 6 3 7
用循环来表示就是:(0)(1,4,2)(3,5,6)(7) ,由 4 个循环组成
所以每有一个循环答案就减 1 ,设循环数为 K ,那么答案就是 2a+b - K。
其实 K 就是等价类数量,考虑怎么求 K 。
先不管储存方式修改过的 B ,看一开始的 B,可以发现,原来在 i 行 j 列的格子,现在在 j 行 i 列。
如果给每个格子编个号(像上面一样),规则是把行和列的二进制拼起来,那么在 i 行 j 列的数就是 i * 2b + j 可以自己手搓一个矩阵验证
也就是说,在 A 变成 B 后,第 i 行 j 列上的数会从 i * 2b + j 变成 j * 2b + i。
可以发现,当 B 变成矩阵三的形式时,依然满足这一点,即第 i 行 j 列上的数是 j * 2b + i。
在二进制意义下,从 i * 2b + j 变成 j * 2b + i ,相当于把这个二进制数看成一个项链,这个项链向右转了 b 位。比如第 2 行第 1 列原本是 5,即 101,在转换后就变成了 110,即 6,相当于 101 向右转了 1 位。
也就是说,假如二进制数 A 向右转 b 位能得到二进制数 B,那么 A 和 B 属于同一个等价类。
现在问题转化成:一条长度为 a + b 的项链,每个位置可以染 0 和 1 两种颜色,一条项链向右转 b 位得到的项链我们认为和原项链相同,问有多少种不同的染色方式。
看起来很像 Polya 定理的经典项链染色问题,但是这里规定要转 b 位而不是 1 位。
有一个结论:对于一个长度为 n 的环,假如将每隔 m 位的位置分在同一组,那么最后一共有 gcd(n,m) 组,每组包含 n/gcd(n,m) 个元素。
这个每隔 m 位分在同一组,其实等价于向右转 m 位,假如认为相同的项链在同一组内,带到上面的问题就能得到这条项链一共可以分成 gcd(a+b,b) = gcd(a,b) 组。
因为一共有 gcd(a,b)
再看一开始的条件:向右转 b 位得到的项链和原项链相同。现在变成了转 b/gcd(a,b) 位。
此时因为 gcd((a+b)/gcd(a,b),b/gcd(a,b)) = 1,这说明,假如对这个由大珠子串成的项链再进行像上面那样的颜色分组,那么最后只会有 1组,也就是说,所有珠子在同一组内。
这等价于,向右转 1次得到的项链,与原本的项链相同。
诶,这不就是经典的项链染色问题了嘛,套一下 Polya 定理即可。
设 n = (a+b)/gcd(a,b),那么有:
K = 1/n Σ(i=1~n) colorm(i)
其中,col = 2gcd(a,b) 表示每颗大珠子可以染的颜色数量,因为每颗大珠子里面包含 gcd(a,b) 颗小珠子,每颗小珠子有两种颜色可以染,所以大珠子就有 2gcd(a,b) 种颜色。
然后,m(i) 表示置换 i 的循环数量,即 gcd(i,n)。
参考模板题,转化一下柿子,最后得到:
ans = 2a+b - K = 2a+b - 1/n Σ(d|n) 2gcd(a,b) * d φ(n/d)
Code:
1 #include<bits/stdc++.h> 2 #define int long long 3 using namespace std; 4 const int maxn=1000001; 5 const int mod=1000003; 6 int T,a,b,n,m,cnt,G,ar[maxn],br[maxn]; 7 int num,ans,len,phi[maxn],pri[maxn],p[maxn]; 8 bool nop[maxn]; 9 inline void init(){ 10 nop[1]=1; 11 phi[1]=1; 12 for(int i=1;i<maxn;i++){ 13 if(!nop[i]){ 14 pri[++cnt]=i; 15 phi[i]=(i-1)%mod; 16 } 17 for(int j=1;j<=cnt;j++){ 18 if(i*pri[j]>maxn) break; 19 nop[i*pri[j]]=1; 20 if(i%pri[j]==0){ 21 phi[i*pri[j]]=phi[i]*pri[j]%mod; 22 break; 23 } 24 phi[i*pri[j]]=phi[i]*phi[pri[j]]%mod; 25 } 26 } 27 p[0]=1; 28 for(int i=1;i<=maxn;i++){ 29 p[i]=(p[i-1]*2)%mod; 30 } 31 } 32 inline void Break(int x){ 33 num=0; 34 for(int i=1;i<=cnt;i++){ 35 if(pri[i]*pri[i]>n) break; 36 if(x%pri[i]==0){ 37 num++; 38 ar[num]=pri[i]; 39 br[num]=0; 40 while(x%pri[i]==0){ 41 br[num]++; 42 x/=pri[i]; 43 } 44 } 45 } 46 if(x>1){ 47 num++; 48 ar[num]=x; 49 br[num]=1; 50 } 51 } 52 inline void exgcd(int a,int b,int &x,int &y){ 53 if(!b){ 54 x=1; 55 y=0; 56 return ; 57 } 58 else{ 59 exgcd(b,a%b,y,x); 60 y-=a/b*x; 61 } 62 } 63 inline void dfs(int x,int k){ 64 if(k>num){ 65 ans=(ans+(p[x*G]*phi[len/x])%mod)%mod; 66 return ; 67 } 68 for(int i=0;i<=br[k];i++){ 69 dfs(x,k+1); 70 x*=ar[k]; 71 } 72 } 73 inline int gcd(int x,int y){ 74 return y==0?x:gcd(y,x%y); 75 } 76 signed main(){ 77 scanf("%d",&T); 78 init(); 79 while(T--){ 80 scanf("%d%d",&a,&b); 81 if(!a||!b){ 82 cout<<"0"<<endl; 83 continue; 84 } 85 n=a+b; 86 G=gcd(a,b); 87 len=n/G; 88 Break(len); 89 ans=0; 90 dfs(1,1); 91 int x,y; 92 exgcd(len,mod,x,y); 93 x=(x%mod+mod)%mod; 94 ans=(ans*x)%mod; 95 ans=p[n]-ans; 96 ans=(ans%mod+mod)%mod; 97 cout<<ans<<endl; 98 } 99 return 0; 100 }
T3:第114514类斯特林数
Problem:
你和你的hxd正在透彻线性代数,你的另一个hxd开门进来来了。。。“也许我来的不是时候”“不,你来得正是时候”,于是你们三个开始。。。嘿嘿嘿,透彻组合数学。你们遇到了组合数学的经典例题:给定一个n*m的网格图,每次只能向右下走,求从(1,1)走到(n,m)的方案数。
”这不是sb题吗,秒了秒了。”你如是的说到
秒了这题以后,你的hxd又想出了一个加强版:如果在某些格子上放有galgame,同样每次只能向右下走,问至少多少次能玩到所有的galgame。
“这不还是sb,秒掉”你再一次秒掉了。
你的hxd们不甘心,联合出了一个Pro++版本:在加强版的基础上,每个格子有不止一款galgame,但每次经过时只能玩到一款,问玩到所有的galgame至少要走多少次?
Solution:
脑瘫DP
题目要算至少取几次能取完,
我们考虑有哪一些财宝是不能同一次取的。
由于原题要求是从左上到右下的,我们就从右上到左下走,寻找一次性不能取的。
这种类型题,我们考虑dp。
设 f [i] [j] 代表从(1,m)到(i,j)不能同一次取的有多少,
我们知道(i,j)和右上(i-1,j)是不能同时取的,
所以f[i][j]=f[i-1][j+1]+a[i][j],
我们同时考虑到(i,j)可以从(i-1,j)和(i,j+1)转移过来,所以
最终dp转移是这样的;
f[i][j]=max(f[i-1][j+1]+a[i][j],f[i-1][j]),f[i][j+1]));
Code:
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=1010; 4 int T,n,m,f[maxn][maxn]; 5 int a[maxn][maxn]; 6 int main(){ 7 scanf("%d",&T); 8 while(T--){ 9 memset(f,0,sizeof(f)); 10 scanf("%d%d",&n,&m); 11 for(int i=1;i<=n;i++){ 12 for(int j=1;j<=m;j++){ 13 scanf("%d",&a[i][j]); 14 } 15 } 16 for(int i=n;i>=1;i--){ 17 for(int j=1;j<=m;j++){ 18 f[i][j]=f[i+1][j-1]+a[i][j]; 19 f[i][j]=max(f[i][j],f[i+1][j]); 20 f[i][j]=max(f[i][j],f[i][j-1]); 21 } 22 } 23 cout<<f[1][m]<<endl; 24 } 25 return 0; 26 }
T4:关于你玩完galgame并分类后去重这件事
Problem:
如题 , 你玩完galgame后,你从中挑选出了几个,但是你发现它们中间有重复的,所以你打算去重 , 去重时你会从中随机选取三个区间 , 若一款galgame在三个区间中的出现了 , 那么它就重复了 ,这样的gal不要也罢 。现在你想知道去重之后还剩下多少款gal。
Solution:
设cnt[i]表示第i个数在询问区间中的出现次数
那么第 i 个询问的答案为 3 + r1 - l1 + r2 - l2 + r3 - l3 - 3*Σ(x=1) min(cnt1[x],cnt2[x],cnt3[x])
后面的那一坨可以用莫队处理
具体做法是对于每个询问维护一个bitset
将所有数离散化之后维护出每个数的出现次数即可
但是这样显然是会超空间的,于是我们把每25000个询问一起做就可以了
Code:
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=100010; 4 const int maxm=25000; 5 int n,m,a[maxn],d[maxn],bl[maxn]; 6 int L[maxn][5],R[maxn][5],base,cnt=0; 7 int f[maxn],ton[maxn],ans[maxn]; 8 bitset<maxn>bit[maxm+50]; 9 bitset<maxn>now; 10 struct node{ 11 int l,r,opt,id; 12 friend bool operator < (node a,node b){ 13 return bl[a.l]==bl[b.l]?bl[a.r]<bl[b.r]:bl[a.l]<bl[b.l]; 14 } 15 node(){} 16 node(int a,int b,int c,int d){ 17 l=a; 18 r=b; 19 opt=c; 20 id=d; 21 } 22 }q[maxn*3+1]; 23 inline void add(int x){ 24 ton[a[x]]++; 25 now[a[x]+ton[a[x]]-1]=1; 26 } 27 inline void delet(int x){ 28 now[a[x]+ton[a[x]]-1]=0; 29 ton[a[x]]--; 30 } 31 inline void solve(int ll,int rr){ 32 memset(f,0,sizeof(f)); 33 memset(ton,0,sizeof(ton)); 34 memset(ans,0,sizeof(ans)); 35 now.reset(); 36 for(int i=0;i<=maxm;i++){ 37 bit[i].reset(); 38 } 39 cnt=0; 40 for(int j=ll;j<=rr;j++){ 41 for(int i=1;i<=3;i++){ 42 q[++cnt].l=L[j][i]; 43 q[cnt].r=R[j][i]; 44 q[cnt].opt=i; 45 q[cnt].id=j-ll+1; 46 ans[j-ll+1]+=(R[j][i]-L[j][i]+1); 47 } 48 } 49 sort(q+1,q+1+cnt); 50 int l=1,r=0; 51 for(int i=1;i<=cnt;i++){ 52 while(l>q[i].l) add(--l); 53 while(r<q[i].r) add(++r); 54 while(l<q[i].l) delet(l++); 55 while(r>q[i].r) delet(r--); 56 if(!f[q[i].id]){ 57 bit[q[i].id]=now; 58 f[q[i].id]=1; 59 } 60 else bit[q[i].id]&=now; 61 } 62 for(int i=ll;i<=rr;i++){ 63 printf("%d\n",ans[i-ll+1]-bit[i-ll+1].count()*3); 64 } 65 } 66 int main(){ 67 scanf("%d%d",&n,&m); 68 base=sqrt(n); 69 for(int i=1;i<=n;i++){ 70 scanf("%d",&a[i]); 71 d[i]=a[i]; 72 bl[i]=(i-1)/base+1; 73 } 74 sort(d+1,d+n+1); 75 for(int i=1;i<=n;i++){ 76 a[i]=lower_bound(d+1,d+1+n,a[i])-d; 77 } 78 for(int i=1;i<=m;i++){ 79 for(int j=1;j<=3;j++){ 80 scanf("%d%d",&L[i][j],&R[i][j]); 81 } 82 } 83 for(int i=1;i<=n;i+=maxm+1){ 84 solve(i,min(i+maxm,m)); 85 } 86 return 0; 87 }