zoukankan      html  css  js  c++  java
  • CF 827E Rusty String FFT

    传送门

    如果没有碍事的?的话,判定字符串的循环节直接用KMP的失配数组就可以搞定。现在有了碍事的?,我们就需要考虑更通用的算法。

    考虑KMP失配数组判定字符串循环节的本质,发现判定(k)是否为字符串的循环节等价于判定字符串在右移(k)位后能否和原字符串匹配(只考虑二者重叠的部分)。

    我们不妨先把?直接看成一个可以匹配任何字符的通配符,而解决带通配符的字符串匹配问题的一个算法就是FFT

    (以下默认下标为(0)~(n-1)

    设字符串为(s),因为字符集只有(2),不妨直接枚举不能匹配的两种情况。定义(a_i=[s_i='V'])(b_i=[s_i='K']),另一种情况只需要把定义反过来就行了。

    再定义一个数组

    [egin{align}c_i=sum_{j=0}^{n-1}a_{i+j}b_jend{align} ]

    (另一种情况式子是一样的)

    发现(s)右移(i)位后能和原串匹配当且仅当两种情况的(c_i)都为(0)

    而上面的式子把({a_i})反转后就是

    [egin{align}c_i=sum_{j=0}^{n-1}a^R_{n-i-j-1}b_{j}end{align}=left(a^R*b ight)_{n-i-1} ]

    跑两遍卷积即可。

    然而还是会发现一些问题……从样例就能看出来,这里的?其实并不是通配符,因为在判定循环节时一个?在不同的位置必须代表相同的字符(可能有点抽象,参见第一组样例中2为什么可以匹配但不是循环节)。

    然而这个问题其实并不棘手。我们都知道如果一个数真的是循环节那么它的所有倍数也一定是循环节,所以对于那些(c_i)(00)的位置再判断一下它的倍数是否也都满足(c_i=0)就行了。

    (完整证明参见官方题解)

    这一步的复杂度是(O(nlog n))的,不会影响到总复杂度。

    另外,考虑到问题的特殊性,其实不必跑两遍卷积,任意跑其中一种情况即可,两种情况的(c_i)分别是(left(a^R*b ight)_{n-i-1})(+left(a^R*b ight)_{n+i-1})(可以从两种情况的对称性的角度理解)。

    (我比较懒写的是NTT)

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int maxn=1048600,p=998244353,g=3;
    void NTT(int*,int,int);
    int qpow(int,int);
    char s[maxn];
    bool ans[maxn];
    int T,n,A[maxn],B[maxn];
    int main(){
    	scanf("%d",&T);
    	while(T--){
    		scanf("%d%s",&n,s);
    		int N=1;
    		while(N<(n<<1))N<<=1;
    		memset(A,0,sizeof(int)*N);
    		memset(B,0,sizeof(int)*N);
    		for(int i=0;i<n;i++){
    			if(s[i]=='V')A[n-i-1]=1;
    			else if(s[i]=='K')B[i]=1;
    		}
    		NTT(A,N,1);
    		NTT(B,N,1);
    		for(int i=0;i<N;i++)A[i]=(long long)A[i]*B[i]%p;
    		NTT(A,N,-1);
    		int cnt=0;
    		for(int i=1;i<=n;i++){
    			ans[i]=true;
    			for(int j=i;j<=n;j+=i)ans[i]&=!(A[n-j-1]+A[n+j-1]);
    			cnt+=ans[i];
    		}
    		printf("%d
    ",cnt);
    		for(int i=1;i<=n;i++)
    			if(ans[i]){
    				printf("%d",i);
    				if(--cnt)printf(" ");
    			}
    		printf("
    ");
    	}
    	return 0;
    }
    void NTT(int *A,int n,int tp){
    	for(int i=1,j=0,k;i<n-1;i++){
    		k=n;
    		do j^=(k>>=1);while(j<k);
    		if(i<j)swap(A[i],A[j]);
    	}
    	for(int k=2;k<=n;k<<=1){
    		int wn=qpow(g,tp>0?(p-1)/k:p-1-(p-1)/k);
    		for(int i=0;i<n;i+=k){
    			int w=1;
    			for(int j=0;j<(k>>1);j++,w=(long long)w*wn%p){
    				int a=A[i+j],b=(long long)w*A[i+j+(k>>1)]%p;
    				A[i+j]=a+b;
    				if(A[i+j]>=p)A[i+j]-=p;
    				A[i+j+(k>>1)]=a-b;
    				if(A[i+j+(k>>1)]<0)A[i+j+(k>>1)]+=p;
    			}
    		}
    	}
    	if(tp<0){
    		int inv=qpow(n,p-2);
    		for(int i=0;i<n;i++)A[i]=(long long)A[i]*inv%p;
    	}
    }
    int qpow(int a,int b){
    	int ans=1;
    	for(;b;b>>=1,a=(long long)a*a%p)if(b&1)ans=(long long)ans*a%p;
    	return ans;
    }
    
    
  • 相关阅读:
    iOS,Android,WP, .NET通用AES加密算法
    iOS开发笔记-图标和图片大小官方最新标准
    因为对 Docker 不熟悉建了 N 多个 Nginx
    Docker 学习笔记 2019-05-27
    Linux Mint 19.1 安装 Docker 过程笔记
    W600 一块新的 KiCad PCB
    KiCad Mark 点名称
    一次乙型流感记录(2019-05-24)
    为什么不喜欢在 QQ 群里回答问题?
    Git 的两种忽略文件方式 gitignore 和 exclude
  • 原文地址:https://www.cnblogs.com/hzoier/p/9230281.html
Copyright © 2011-2022 走看看