1. 程式人生 > 實用技巧 >2020杭電HDU-6863多校第八場Isomorphic Strings(Hash+學到的新東西)

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;
}