1. 程式人生 > >KMP_字串最小表示_CH1802_Necklace

KMP_字串最小表示_CH1802_Necklace

點此開啟題目頁面

思路分析:

    方法一: 直接使用字串Hash和二分搜尋判斷兩個字串字典序大小關係, 容易給出時間複雜度為O(nlg(n))的解決方案, n為輸入字串的長度, 具體實現見如下AC程式碼:

//CH1802_Necklace
#include <cstdio>
#include <cstring>
using namespace std;
const int MAX = 1e6 + 5, P = 1331;
char s1[MAX << 1], s2[MAX << 1]; int len;
unsigned long long h1[MAX << 1], h2[MAX << 1], ph[MAX << 1];
//如果s[a...a + len - 1] < s[b...b + len - 1]返回true, 否則返回false 
bool cmp(unsigned long long *s, int a, int b){
	//計算最長公共字首
	int l = 0, r = len, mid;
	while(mid = l + r + 1 >> 1, l < r)  
		if(s[a + mid - 1] - s[a - 1] * ph[mid] == s[b + mid - 1] - s[b - 1] * ph[mid])
			l = mid;
		else r = mid - 1;
	if(l == len) return false;
	return s[a + l] - s[a + l - 1] * ph[1] < s[b + l] - s[b + l - 1] * ph[1];	
}
int main(){
	scanf("%s %s", s1 + 1, s2 + 1), len = strlen(s1 + 1);
	memcpy(s1 + len + 1, s1 + 1, len * sizeof(char))
	, memcpy(s2 + len + 1, s2 + 1, len * sizeof(char));
	ph[0] = 1; for(int i = 1; i <= len * 2; ++i) ph[i] = ph[i - 1] * P;
	for(int i = 1; i <= len * 2; ++i) 
		h1[i] = h1[i - 1] * P + (s1[i] - '0' + 1), h2[i] = h2[i - 1] * P + (s2[i] - '0' + 1);
	int ans1 = 1, ans2 = 1; 
	for(int i = 2; i <= len; ++i){
		if(cmp(h1, i, ans1)) ans1 = i;
		if(cmp(h2, i, ans2)) ans2 = i;
	} 
	if(!strncmp(s1 + ans1, s2 + ans2, len))
		printf("Yes\n"), s1[ans1 + len] = '\0', printf("%s\n", s1 + ans1);
	else printf("No\n");
	return 0;
}

方法二: 下面先給出AC程式碼, 然後分析程式的正確性及其時間複雜度

//CH1802_Necklace
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAX = 1e6 + 5;
char s[MAX << 1]; int len;
char ss[MAX << 1];
int main(){
	scanf("%s %s", s + 1, ss + 1), len = strlen(s + 1);
	memcpy(s + len + 1, s + 1, len), memcpy(ss + len + 1, ss + 1, len);
	int l = 1, r = 2;
	while(l <= len && r <= len){
		int i = 0; while(i < len && s[l + i] == s[r + i]) ++i;
		if(i == len) break;
		if(s[l + i] > s[r + i]){
			l = l + i + 1; if(r == l) ++r;
		}
		else{
			r = r + i + 1; if(l == r) ++l;
		}
	}
	int ans1 = min(l, r); l = 1, r = 2;
	while(l <= len && r <= len){
		int i = 0; while(i < len && ss[l + i] == ss[r + i]) ++i;
		if(i == len) break;
		if(ss[l + i] > ss[r + i]){
			l = l + i + 1; if(r == l) ++r;
		}
		else{
			r = r + i + 1; if(l == r) ++l;
		}
	}
	int ans2 = min(l, r);
	if(!strncmp(s + ans1, ss + ans2, len)){
		cout << "Yes" << endl; 
		for(int i = ans1; i <= ans1 + len - 1; ++i) cout << s[i]; cout << endl;
	}
	else cout << "No" << endl;
	return 0;
} 

    設S_{i}表示將字串S[1...len]的S[i]作為第一個字元的迴圈同構串, 在上述程式碼中, 考慮第14至23行的while迴圈有如下迴圈不變式成立,

在每次第14行迴圈頭檢測之前, 對應字串S的最小表示的S_{k}, 始終滿足 k \in \left \{ l \right \}\cup \left \{ r \right \}\cup \left \{ max(l, r) + 1...n \right \}, 下面證明此迴圈不變式的正確性.

    在第一次第14行迴圈頭檢測之前該迴圈不變式顯然成立, 假設第m次第14行迴圈頭檢測之前該不變式成立, 在第m次執行第14至23行迴圈體的過程中, 如果第16行if條件被滿足, 顯然S_{l}=S_{r}, 設t = min(l, r), p = max(l, r), e = |l - r|, 對任意p <= q <= len, 考慮S_{q}, S_{q - e}

, 設字串SS[1...2len]為S重複2次構成, 顯然SS[q - e...t] = SS[q...p], SS[t + 1...q - e -1] = SS[p + 1...q - 1], 因此S_{q}=S_{q - e}, 故此時\left \{ l \right \}\cup \left \{ r \right \}\cup \left \{ max(l, r) + 1...n \right \}中元素均對應S的最小表示, 

    如果第16行if條件不成立,  如果S[l + i] > S[r + i], 那麼任意0 <= v <= i, 均有S[(l + v) mod len] > S[(r + v) mod len], 因此l...l + i範圍內的均非最優解, 對於S[r + i] > S[l + i]有完全類似的結論.至此, 只需稍加思考便可知, 對於第m + 1次迴圈頭檢測上述迴圈不變式依然成立.

    顯然第15行的while迴圈執行O(len)次, 因此上述程式的時間複雜度為O(len)