Educational Codeforces Round 40 I. Yet Another String Matching Problem(①FFT/NTT②集合划分+kmp③bitset)

题目

假设有两个等长的串x和y,保证字符只包含‘a’-'f'的小写字母

你可以每次选择两个字符,不妨设为'a'和'b',把两个串中当前所有'a'都改成'b'

编辑距离为二者改成相同的串的最小修改次数,

比如x串为abcd,而y串为ddcb,则编辑距离为2

第一次可以把a改成b,有x串为bbcd,y串为ddcb

第二次可以把b改成d,有x串为ddcd,y串也为ddcd

 

现给出两个串S和T(|T|<=|S|<=125000),

对于S中每个长度为|T|的子串,输出其与T的编辑距离

abcdefa
ddcb
2 2 3 3

即分别输出abcd、bcde、cdef、defa与ddcb的编辑距离

思路来源

https://www.luogu.com.cn/blog/Lskkkno1/solution-cf954i 集合划分+kmp

https://www.cnblogs.com/cjyyb/p/8798431.html FFT/NTT

https://www.cnblogs.com/Yuhuger/p/8633965.html bitset

题解1

首先,考虑等长怎么做,比如abcd和ddcb,

第一位表示了一种a和d的互斥关系,表明这两种字符在最终的串中要合并成同一种字符

同理第二位,第三位……,

把字母看成点,互斥关系看成边,建一个图,

在相同的连通分量里的点最后需要被合成一个字母,

合成次数,为连通分量里的字母种类数减1

 

然后,考虑怎么用FFT加速,

如样例abcdefa和ddcb,0-based

下标[0,3]与[0,3]对应了ans[0]

下标[1,4]与[0,3]对应了ans[1]

可以发现,S串中下标为x与T串中下标为y的值会在ans[x-y]中被计算

则可以考虑把T串反过来,下标y变为m-1-y,

 

枚举S串中的字母a-f,枚举T串中的字母a-f,

不妨设现在S串中枚举到c,T串中枚举到e,

则将S串中所有c的位置赋1,其余赋0,T反串同理,二者作FFT,

则可以确认c和e在哪些ans中是互斥的,并查集连一连,

枚举所有字母对之后,就可确认所有的互斥关系

代码1(FFT)

FFT跑的比较慢,3697ms,感觉可以改成NTT再交…

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> P;
const int N=1<<18,mod=1e5+3;
double PI=acos(-1.0);
struct C{
	double r,i;
	C(){}
	C(double a,double b){r=a,i=b;}
	C operator + (C x){return C(r+x.r,i+x.i);}
	C operator - (C x){return C(r-x.r,i-x.i);}
	C operator * (C x){return C(r*x.r-i*x.i,r*x.i+i*x.r);}
}w[N],A[N],B[N];
int R[N];
void FFT(C a[],int n){
	for (int i=0;i<n;i++)
		if (i<R[i])
			swap(a[i],a[R[i]]);
	for (int t=n>>1,d=1;d<n;d<<=1,t>>=1)
		for (int i=0;i<n;i+=(d<<1))
			for (int j=0;j<d;j++){
				C tmp=w[t*j]*a[i+j+d];
				a[i+j+d]=a[i+j]-tmp;
				a[i+j]=a[i+j]+tmp;
			}
}
void FFT_times(vector <int> &a,vector <int> &b,vector <int> &c){
	int n,d;
	for (int i=0;i<a.size();i++)
		A[i]=C(a[i],0);
	for (int i=0;i<b.size();i++)
		B[i]=C(b[i],0);
	for (n=1,d=0;n<a.size()+b.size()-1;n<<=1,d++);
	for (int i=0;i<n;i++){
		R[i]=(R[i>>1]>>1)|((i&1)<<(d-1));
		w[i]=C(cos(2*PI*i/n),sin(2*PI*i/n));
	}
	for (int i=a.size();i<n;i++)
		A[i]=C(0,0);
	for (int i=b.size();i<n;i++)
		B[i]=C(0,0);
	FFT(A,n),FFT(B,n);
	for (int i=0;i<n;i++)
		A[i]=A[i]*B[i],w[i].i*=-1.0;
	FFT(A,n);
	c.clear();
	for (int i=0;i<=a.size()+b.size()-2;i++)
		c.push_back(((LL)(A[i].r/n+0.5))%mod);
}
vector<int>tmp;
vector<int>ps[6],pt[6];
char s[N],t[N];
int n,m;
int par[N][6],ans[N];
int find(int id,int x){
    return par[id][x]==x?x:par[id][x]=find(id,par[id][x]);
}
int main(){
    scanf("%s%s",s,t);
    int n=strlen(s),m=strlen(t);
    reverse(t,t+m);
    for(int i=0;i<n;++i){
        int v=s[i]-'a';
        for(int j=0;j<6;++j){
            if(j==v)ps[j].push_back(1);
            else ps[j].push_back(0);
        }
    }
    for(int i=0;i<m;++i){
        int v=t[i]-'a';
        for(int j=0;j<6;++j){
            if(j==v)pt[j].push_back(1);
            else pt[j].push_back(0);
        }
    }
    for(int k=0;k<n-m+1;++k){
        for(int j=0;j<6;++j){
            par[k][j]=j;
        }
    }
    for(int i=0;i<6;++i){
        for(int j=0;j<6;++j){
            tmp.clear();
            FFT_times(ps[i],pt[j],tmp);
            for(int k=0;k<n-m+1;++k){
                if(tmp[k+m-1]){
                    int x=find(k,i),y=find(k,j);
                    if(x==y)continue;
                    par[k][y]=x;
                    ans[k]++;
                }
            }
        }
    }
    for(int k=0;k<n-m+1;++k){
        printf("%d%c",ans[k]," \n"[k==n-m]);
    }
	return 0;
}

题解2

枚举集合划分,表示哪几个字母在同一个等价类里,种类数是贝尔数的

从小到大dfs字母,考虑把当前字母放到哪一个集合里,或者自己创一个集合

同一个集合的字母视为同一等价类,可以看成是相同的模糊匹配字母,

这样,只要|S|的子串和T模糊意义下完全匹配,就表示该集合划分是合法的,该过程可以魔改kmp

能这么做,是因为贝尔数B(6)=203

代码2

#include<bits/stdc++.h>
using namespace std;
const int N=1<<18,INF=0x3f3f3f3f;
char s[N],t[N];
int n,m,par[6],nex[N],c;
int ans[N];
void solve(int x){
    int i=0,j=nex[0]=-1;
    while(i<m){
        while(j!=-1 && par[t[i]-'a']!=par[t[j]-'a']){
            j=nex[j];
        }
        nex[++i]=++j;
    }
    i=0,j=0;
    while(i<n){
        while(j!=-1 && par[s[i]-'a']!=par[t[j]-'a']){
            j=nex[j];
        }
        ++i;++j;
        if(j==m){
            ans[i-m]=min(ans[i-m],x);
        }
    }
}
void dfs(int x){
    if(x==6){
        solve(6-c);//c个连通块 c种字母保留 6-c个字母要删
        return;
    }
    par[x]=++c;
    dfs(x+1);
    c--;
    for(int i=1;i<=c;++i){
        par[x]=i;
        dfs(x+1);
    }
}
int main(){
    scanf("%s%s",s,t);
    n=strlen(s);m=strlen(t);
    memset(ans,INF,sizeof ans);
    dfs(0);
    for(int i=0;i<=n-m;++i){
        printf("%d%c",ans[i]," \n"[i==n-m]);
    }
	return 0;
}

题解3:还有bitset的硬核暴力做法,O(6*6*m*(n-m)/64),就不考虑了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值