1. 程式人生 > 實用技巧 >「題解」 BalticOI 2014 Day 1 三個朋友

「題解」 BalticOI 2014 Day 1 三個朋友

Problem

題目描述
給定一個字串 \(S\) ,先將字串 \(S\) 複製一次(變成雙倍快樂),得到字串 \(T\) ,然後在 \(T\) 中插入一個字元,得到字串 \(U\)

給出字串 \(U\) ,重新構造出字串 \(S\)

輸入格式
第一行一個整數 \(N\) ,表示字串 \(U\) 的長度。

第二行一個長度為 \(N\) 的字串,表示字串 \(U\)

輸出格式
一行一個字串,表示字串 \(S\)

特別地:
如果字串無法按照上述方法構造出來,輸出 NOT POSSIBLE
如果字串 不唯一,輸出 NOT UNIQUE

Solution

前置知識

這道題用到的方法是進位制 \(hash\)

,顧名思義將字串轉化為 \(k\) 進位制數,具體的方法如下:

const int MAXN = 1005;
char s[MAXN];
int hash[MAXN], k;

scanf("%s", s + 1);
for (int i = 1; i <= strlen(s + 1); i++) {
	hash[i] = hash[i - 1] * k + s[i]; //將字串轉化為 k 進位制數
}

當我們要求一段子串(或者說是一段區間)的 \(hash\) 值時又該怎麼辦呢?

設這段子串在 \([l,r]\) ,而
\(hash[l] = hash[l-1]*k+s[l]\)

\(hash[l+1] = hash[l]*k+s[l+1] = (hash[l-1]*k+s[l])*k+s[l+1]=hash[l-1]*k^2+s[l]*k+s[l+1]\)

\(…\)

容易知道 \(hash[l+1]\) 中包含了 \(hash[l-1]*k^{l+1-l+1}\)\([l,l+1]\)\(hash\) 值。同理 \(hash[r]\) 中包含了 \(hash[l-1]*k^{r-l+1}\)\([l,r]\)\(hash\) 值,所以求\([l,r]\)\(hash\) 值可以用 \(hash[r]\) 減去 \(hash[l-1]*k^{r-l+1}\)

int Get_Hash(int l, int r) {
	return hash[r] - hash[l - 1] * pre[r - l + 1]; //pre[i]=k^i
}

從這個問題可以擴充套件到另一個問題,求 \([l,r]\) 刪除下標位 \(x\) 的字元後的 \(hash\) 值。
對於這個問題我們可以先 \([l,x-1],[x+1,r]\)\(hash\) 值,然後將 \(hash(l,k-1)*k^{r-(x+1)+1}\)\(hash(x+1,r)\) 相加即可。這個的道理和求 \(hash\),減去 \(hash[l-1]*k^{r-l+1}\) 是一樣的。

int Get_Sum(int l, int r, int k) {
	return Get_Hash(l, k - 1) * pre[r - k] + Get_Hash(k + 1, r);
}

知道了這兩個問題如何求解就可以輕鬆解決本題了。

思路

很容易知道 \(N\) 為偶數時是一定不行的

首先按照字元的位置可以分為兩種個情況:

  1. \([1,(N+1)/2]\)
    若在這之間,則 \(S\) 就是 \(s[(N+1)/2+1——N]\)
    我們可以 \([1,(N+1)/2]\) 做迴圈,列舉字元的位置,然後根據 \(hash\) 值來判斷是否相等
string str1, ans1;

int mid = (N + 1) / 2;
for (int i = mid + 1; i <= n; i++) {
		str1.push_back(s[i]);
	}
for (int i = 1; i <= mid; i++) {
		if (Get_Sum(1, mid, i) == Get_Hash(mid + 1, n)) {
			ans++;
			ans1 = str1;
			break;
		} 
	}
  1. \([(N+1)/2,N]\)
    若在這之間,則 \(S\) 就是 \(s[1——(N+1)/2-1]\)
    同理。
string ans2, str2;

int mid = (N + 1) / 2;
for (int i = 1; i < mid; i++) {
		str2.push_back(s[i]);
	}
for (int i = mid; i <= n; i++) {
		if (Get_Sum(mid, n, i) == Get_Hash(1, mid - 1)) {
			ans++;
			ans2 = str2;
			break;
		}
	}

在這之後,需要分出三種情況:無解?唯一解?多解?

1.當 \(ans == 0\) 一定無解,直接輸出NOT POSSIBLE
2.當 \(ans==1\)\(ans1 == ans2\), 即兩種情況下只有一種或者兩種情況得到的是相同的解都是輸出 \(ans1/ans2\).
3.上述兩種情況都不滿足,就肯定是多解了,輸出NOT UNIQUE

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;

const int MAXN = 2e6 + 5;
#define base 131
#define ull unsigned long long

int n, mid, ans;
string str1, str2, ans1, ans2;
char s[MAXN];
ull tmpl, tmpr;
ull pre[MAXN], hash[MAXN];

ull Get_Hash(int l, int r) {
	return hash[r] - hash[l - 1] * pre[r - l + 1];
}

ull Get_Sum(int l, int r, int k) {
	return Get_Hash(l, k - 1) * pre[r - k] + Get_Hash(k + 1, r);
}

int main() {
	scanf("%d %s", &n, s + 1);
	mid = (n + 1) >> 1;
	if (!(n & 1)) {
		return printf("NOT POSSIBLE"), 0;
	}
	pre[0] = 1;
	for (int i = 1; i <= n; i++) {
		pre[i] = pre[i - 1] * base;
		hash[i] = hash[i - 1] * base + (s[i] - 'A' + 1);
	}
	
	tmpr = Get_Hash(mid + 1, n);
	for (int i = mid + 1; i <= n; i++) {
		str1.push_back(s[i]);
	}
	for (int i = 1; i <= mid; i++) {
		tmpl = Get_Sum(1, mid, i);
		if (tmpl == tmpr) {
			ans++;
			ans1 = str1;
			break;
		} 
	}
	
	tmpl = Get_Hash(1, mid - 1);
	for (int i = 1; i < mid; i++) {
		str2.push_back(s[i]);
	}
	for (int i = mid; i <= n; i++) {
		tmpr = Get_Sum(mid, n, i);
		if (tmpl == tmpr) {
			ans++;
			ans2 = str2;
			break;
		}
	}
	
	if (!ans) printf("NOT POSSIBLE");
	else if (ans == 1 || ans1 == ans2) cout << (ans1.size() ? ans1 : ans2);
	else printf("NOT UNIQUE");
	return 0;
}