「题解」 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;
}
上一篇:Python实现LRFM模型分析客户价值


下一篇:Bash 破壳漏洞Shellshock (CVE-2014-6271)复现分析