「Solution」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;
}
posted @ 2021-01-09 15:25  PoisonNNN  阅读(99)  评论(0编辑  收藏  举报