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串完全相同

这个问题显然用KMP就无法解决了,我们还是考虑和上面类似的方法。那么我们回顾上面的普通串匹配过程,我们可以总结出思路大概是这样的:

  1. 定义匹配函数

  2. 定义完全匹配函数

  3. 快速计算每一位的完全匹配函数值

有了通配符,我们肯定需要重新定义一个匹配函数

我们设通配符的数值为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:

 你正在学习,你的hxd开门进来,恰好你家中无人,于是你们俩.....嘿嘿嘿,拿出一本线性代数开始透彻。在书中你们遇见了这样一个问题:给定一个 2a *2b 的矩阵,每次可以选择两个元素进行交换,问将其交换成其转置矩阵的最小操作数。答案对1000003 取模。

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 个循环组成

可以发现,在一个长度为 k 的循环内,只需要 k − 1 次交换就够了。

所以每有一个循环答案就减 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) 组,所以从任意一个位置出发,(算上起点)往后的 gcd(a,b) 个位置的颜色一定都不相同,并且恰好包含  gcd(a,b) 种颜色,不妨将每 gcd(a,b) 个位置算成一颗大珠子,那么项链就变成了由 ​(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 }
posted @ 2021-10-09 12:24  B_lank  阅读(64)  评论(1编辑  收藏  举报