1. 程式人生 > 實用技巧 >項鍊(最小表示法)

項鍊(最小表示法)

題目

題目

思路

看到這道題目我腦子裡面第一個閃過的是KMP,但是看到第二問我就發現竟然是我不會的最小表示法。

首先明確一個思路,如果對於兩個東西我們要確定是否相同,最好的方法就是確定一個最小的東西判斷相等,例如在AcWing 156. 矩陣 中就是最小的瀏覽順序,而這裡則是最小的字典序,所以我們不難想到最小表示法。

什麼?你說我總是講的不好,我也沒打算講啊,這個演算法OIwiki講的挺好的,我就只做一些註釋吧以及答疑吧。

首先,暴力是我們每次比較\(i\)\(j\)開始的迴圈同構,把當前比較到的位置記作 ,每次遇到不一樣的字元時便把大的跳過,最後剩下的就是最優解。

int k = 0, i = 0, j = 1;
while (k < n && i < n && j < n) {
  if (sec[(i + k) % n] == sec[(j + k) % n]) {
    ++k;
  } else {
    if (sec[(i + k) % n] > sec[(j + k) % n])
      ++i;
    else
      ++j;
    k = 0;
    if (i == j) i++;
  }
}
i = min(i, j);

但是這個很明顯在\(aaaaa...b\)時炸了,這個演算法是\(O(n^2)\)的。

但是,我們可以優化!

int k = 0, i = 0, j = 1;
while (k < n && i < n && j < n) {
  if (sec[(i + k) % n] == sec[(j + k) % n]) {
    k++;
  } else {
    sec[(i + k) % n] > sec[(j + k) % n] ? i = i + k + 1 : j = j + k + 1;
    if (i == j) i++;
    k = 0;
  }
}
i = min(i, j);

至於正確性嗎,沒優化的肯定是對的,優化並沒有改變正確性,所以也是對的,沒有毛病。

問題一

為什麼是最後是選擇\(min(i,j)\)呢?

首先看看退出的條件,那麼在\(i,j\)跳出\(n\)的範圍是,很明顯\(min\)會選另外一個,但是\(k=n\)時呢?說明兩個都是最小表示,隨便選一個都可以。

問題二

\(k=n\)是個什麼情況?

說明同時有兩個位置都是最小表示法,那麼這個字串肯定是由一個迴圈節迴圈組成的。

有人會問會不會是\(n\)個迴圈節加上半個迴圈節呢?

不會的,你畫個圖就會發現這樣子的話\(k\)是不可能等於\(n\)的。

程式碼

#include<cstdio>
#include<cstring>
#define  N  2100000
using  namespace  std;
char  a[N],b[N];
int  n;
int  zxbsf(char  *s)//求最小表示法的位置
{
	int  i=1,j=2,k=0;
	while(i<=n  &&  j<=n  &&  k<n)
	{
		if(s[i+k]==s[j+k])k++;
		else
		{
			if(s[i+k]>s[j+k])i=i+k+1;
			else  j=j+k+1;
			if(i==j)j++;
			k=0;
		}
	}
	return  i<=n?i:j;//其實和min大同小異。
}
int  main()
{
	scanf("%s",a+1);scanf("%s",b+1);n=strlen(a+1);
	for(int  i=2*n;i>n;i--)a[i]=a[i-n],b[i]=b[i-n];//複製一份
	int  x=zxbsf(a),y=zxbsf(b);
	int  t=0;
	for(int  i=0;i<n;i++)
	{
		if(a[x+i]!=b[y+i])
		{
			t=1;
			break;
		}
	}
	if(t==1)printf("No\n");
	else
	{
		printf("Yes\n");
		for(int  i=0;i<n;i++)printf("%c",a[x+i]);
		printf("\n");
	}
	return  0;
}