KMP_字符串最小表示_CH1802_Necklace

版权声明:本文为博主原创作品, 转载请注明出处! https://blog.csdn.net/solider98/article/details/83824853

点此打开题目页面

思路分析:

    方法一: 直接使用字符串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)

猜你喜欢

转载自blog.csdn.net/solider98/article/details/83824853
今日推荐