2020杭电HDU-6863多校第八场Isomorphic Strings(Hash+学到的新东西)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6863
CSDN食用链接:https://blog.csdn.net/qq_43906000/article/details/107991168

Problem Description

It is preferrable to read the pdf statment.

Two strings are called cyclical isomorphic if one can rotate one string to get another one. 'Rotate' here means ''to take some consecutive chars (maybe none) from the beginning of a string and put them back at the end of the string in the same order''. For example, string ''abcde'' can be rotated to string ''deabc''.

Now that you know what cyclical isomorphic is, Cuber QQ wants to give you a little test.

Here is a string s of length n. Please check if s is a concatenation of k strings, \(s_1,s_2,⋯,s_k (k>1)\), where,

1.k is a divisor of n;

2.\(s_1,s_2,…,s_k\) are of equal length: \(\frac{n}{k}\);

3.There exists a string t, which is cyclical isomorphic with si for all \(1≤i≤k\).

Print ''Yes'' if the check is positive, or ''No'' otherwise.

Input
The first line contains an integer \(T (1≤T≤1000)\), denoting the number of test cases. T cases follow.

The first line of each test case contains an integer \(n (1≤n≤5⋅10^6)\).

The second line contains a string s of length n consists of lowercase letters only.

It is guaranteed that the sum of n does not exceed \(2⋅10^7\).

Output
For each test case, output one line containing ''Yes'' or ''No'' (without quotes).
Sample Input
6
1
a
2
aa
3
aab
4
abba
6
abcbcc
8
aaaaaaaa

Sample Output
No
Yes
No
Yes
No
Yes

题目大意:给你一个字符串,你现在需要将其分为若干份,使得每一份之间相互循环同构,份数必须大于1,问你能否构造出这样的若干个字符串。

emmmmm,这题使我受益良多。。。。首先,一些坏毛病在此时凸显了出来,我写了个continue,然后一直没注意到漏掉了一些东西,结果使得我们整个队WA了一下午。。。QAQ,所以对于continue的使用一定要谨慎,哪怕多写几个if-else也尽量避免continue的使用。接下来就是对于标记数组的使用,我也是长见识了,标记数组可以不用清空,只需要我们每次改变一下对应的值就好了,真是非常奇妙的用法。。。。。

首先我们可以考虑一下暴力做法,我们直接筛出所有n的因子,然后将其当作每份的长度k,然后我们就可以用\(O(n)\)的复杂度判断这个k是否合法,这个判断可以用Hash,我们先第一份的Hash值存起来,然后求将其所有的循环同构的字符串,总共\(k\)个,然后存下来就好了:

ll p=0;
for (int i=0; i<len; i++)
	p=(p*base+s[i]-'a'+1)%mod;
vis[p]=cnt;
int tp=s[0]-'a'+1,num=1;
for (int i=1; i<len; i++) {
	ll pp=((p-(1LL*tp*pw[len-1]%mod)+mod)%mod*base%mod+tp)%mod;
	vis[pp]=cnt;
	p=pp;
	tp=s[num++]-'a'+1;
}

接下来我们对每个长度为k的字符串判断是否在这些循环同构中就好了:

for (int i=1; i<n/len; i++) {
	ll use=0;
	for (int j=i*len; j<(i+1)*len; j++) {
		use=(use*base+s[j]-'a'+1)%mod;
	}
	if (vis[use]!=cnt) return 0;
}

以下是暴力AC代码:(需要4000ms+)

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int mac=5e6+10;
const int mod=7000061;
// const int MOD=1e9+7;
const int base=29;

char s[mac];
int nb[30];
int vis[mac<<1];
ll pw[mac];
int cnt=0;

int check(int n,int len)
{	
	cnt++;
    ll p=0;
    for (int i=0; i<len; i++)
    	p=(p*base+s[i]-'a'+1)%mod;
    vis[p]=cnt;
    int tp=s[0]-'a'+1,num=1;
    for (int i=1; i<len; i++) {
    	ll pp=((p-(1LL*tp*pw[len-1]%mod)+mod)%mod*base%mod+tp)%mod;
        vis[pp]=cnt;
        p=pp;
        tp=s[num++]-'a'+1;
    }
    for (int i=1; i<n/len; i++){
        ll use=0;
        for (int j=i*len; j<(i+1)*len; j++){
            use=(use*base+s[j]-'a'+1)%mod;
        }
        if (vis[use]!=cnt) return 0;
    }
    return 1;
}

int main(int argc, char const *argv[])
{
    int t;
    scanf ("%d",&t);
    pw[0]=1;
    for (int i=1; i<mac-5; i++) pw[i]=pw[i-1]*base%mod;
    while (t--){
        int n;
        scanf ("%d",&n);
        scanf ("%s",s);
        int mk=0;
        for (int i=1; 1LL*i*i<=n; i++){
        	if (n%i) continue;
        	if (n/i==1) continue;
        	int ok=check(n,i);
        	if (ok) {mk=1; break;}

        	int fac=n/i;
        	if (n/fac==1) continue;
        	ok=check(n,fac);
        	if (ok) {mk=1; break;}
        }
        if (mk) printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}

接下来我们可以考虑优化一下,我们可以思考,如果某个字母只出现了1次,那么这整个字符串一定不可能分为若干份的循环同构,若干某个字母出现了\(x\)次,那么该字符串只能被分为\(fac(x)\)段,其表示为x的因子,因为每一段的某些字母的出现次数是一定要相等的。也说我们只需要找到出现次数最少的那个次数,然后对其分解因子,接着对因子进行判断即可,那么就可以大大优化时间了。

以下是AC代码:(优化后只需要600ms+)

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int mac=5e6+10;
const int mod=7000061;
// const int MOD=1e9+7;
const int base=29;

char s[mac];
int nb[30];
int vis[mac<<1];
ll pw[mac];
int cnt=0;

int check(int n,int len)
{	
	cnt++;
    ll p=0,ps=0;
    for (int i=0; i<len; i++)
    	p=(p*base+s[i]-'a'+1)%mod;
    vis[p]=cnt;
    int tp=s[0]-'a'+1,num=1;
    for (int i=1; i<len; i++) {
    	ll pp=((p-(1LL*tp*pw[len-1]%mod)+mod)%mod*base%mod+tp)%mod;
        vis[pp]=cnt;
        p=pp;
        tp=s[num++]-'a'+1;
    }
    for (int i=1; i<n/len; i++){
        ll use=0,uses=0;
        for (int j=i*len; j<(i+1)*len; j++){
            use=(use*base+s[j]-'a'+1)%mod;
        }
        if (vis[use]!=cnt) return 0;
    }
    return 1;
}

int main(int argc, char const *argv[])
{
    int t;
    scanf ("%d",&t);
    pw[0]=1;
    for (int i=1; i<mac-5; i++) pw[i]=pw[i-1]*base%mod;
    while (t--){
        int n;
        scanf ("%d",&n);
        scanf ("%s",s);
        int mk=0,mi=1<<30;
        memset(nb,0,sizeof nb);
        for (int i=0; i<n; i++) nb[s[i]-'a']++;
        for (int i=0; i<26; i++) 
        	if (nb[i])
 		       	mi=min(mi,nb[i]);

        if (mi==1) {printf("No\n"); continue;}
    	for (int i=1; 1LL*i*i<=mi; i++){
    		if (mi%i) continue;//!!!注意n%i的时候不能continue
    		int ok=0;
    		if (i>1 && n%i==0) ok=check(n,n/i);
    		if (ok) {mk=1; break;}

    		int fac=mi/i;
    		if (fac==i) continue;
    		if (fac==1) continue;
    		if (n%fac) continue;
    		ok=check(n,n/fac);
    		if (ok) {mk=1; break;}
    	}
        if (mk) printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}
posted @ 2020-08-13 21:18  lonely_wind  阅读(368)  评论(0编辑  收藏  举报