「題解」 BalticOI 2014 Day 1 三個朋友
Problem
題目描述
給定一個字串 \(S\) ,先將字串 \(S\) 複製一次(變成雙倍快樂),得到字串 \(T\) ,然後在 \(T\) 中插入一個字元,得到字串 \(U\) 。
給出字串 \(U\) ,重新構造出字串 \(S\) 。
輸入格式
第一行一個整數 \(N\) ,表示字串 \(U\) 的長度。
第二行一個長度為 \(N\) 的字串,表示字串 \(U\) 。
輸出格式
一行一個字串,表示字串 \(S\) 。
特別地:
如果字串無法按照上述方法構造出來,輸出 NOT POSSIBLE;
如果字串 不唯一,輸出 NOT UNIQUE。
Solution
前置知識
這道題用到的方法是進位制 \(hash\)
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,(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;
}
}
- 在 \([(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;
}