#2823. 「BalticOI 2014 Day 1」三个朋友 题解
【杂言】:
前置知识为\(Hash\) , 我的 理解
【题目描述】:
给定一个字符串\(S\),先将字符串\(S\)复制一次(变成双倍快乐),得到字符串\(T\),然后在\(T\)中插入一个字符,得到字符串\(U\)。
给出字符串\(U\),重新构造出字符串\(S\)。(字符串\(S\)的长度 \(\leq 2e7+1\))
【思路】:
首先 , 看到构造字符串, 我们就想到了字符串匹配(然后你就想到了\(KMP\)) , 然后你就想到了用哈希干掉这道题(请忽略我看了题目标签,谢谢合作) 。
然后, 我们再次发现,对于得到的新的字符串,一定不会是偶数,因为\(copy\)过后,又加了1,也就是长度应该为\(2k+1\)(其中\(k\)为字符串\(S\)的长度) 。那么就找出一个不可能的条件
再然后 ,没有一眼结论了,那么好, 我们换思路考虑一下 ,我们很自然的可以想到,我只需要枚举去掉一个字符之后,判断一下,两边是否相等,如果相等,那么也就必然能够表示出来
同时我们也出现一个疑惑 , 那就是如何判断一下形成字符串\(S\)是否是唯一的呢? ,很麻烦 ,我们需要继续分类,然后看一下是否还能分(为之后的优化打下坚实的基础)
联想一下哈希, 我们发现 ,我们预处理出前缀的哈希值 , 我们还是枚举多余字符的位置,同时,我们根据其位置与 \(\frac{n}{2}\)与\(\frac{n}{2} + 1\)的关系,来判断并求解出前后两个字符串的哈希值(由于这是一个\(copy\),所以只需要比较第\(\frac{n}{2}\)和\(\frac{n}{2}+1\)即可) 。
我们假设 \(x\)为前半部分\(hash\)值,\(y\)为后半部分\(hash\)值 ,
当我们枚举的时候,我们必然是要看一下删除完的字符串的\(Hash\)值, 那么其中原来的\(Hash\)怎么处理呢?(也就是刚刚我们假设的\(x\)),我们来look一下这个图
,
当枚举到左边的那一个字符时,
同时, \(1……i-1\)的\(Hash\)值为 : \(hash_{i-1}\) ,由于要和\(i+1,i+2 , …… \frac{n}{2} +1\)组成一个部分,也就是要对齐,所以,
应该乘以一个\(power_{\frac{n}{2} - i + 1}\) ,其中\(\frac{n}{2} - i +1\)表示的就是\(i+1,i+2,…… , \frac{n}{2} +1\)的长度。
枚举在左边的处理完了,接下来看一下枚举右边的,这时候左边是完全不需要动的, 我们只需要动一下后面,让他们对齐,
当枚举到左边的那一个字符时,
这个式子很明显,比上一个式子简单,且考虑的东西明显要少。
\(i-1-\frac{n}{2}\)表示的是有半部分从开始到\(i-1\)的长度,
其开始位置为$\frac{n}{2} +1 $ ,\(n-i\)是后面的的长度,为了让他们对齐, 所以要乘以一个\(power_{n-i}\)
然后我们可以考虑一下我们的问题了, 我们的唯一解了。 其实也是十分的简单, 我们只需要在进行搜索到的时候一个的时候,记录一下第一个可以得到字符串\(S\)的位置,同时在进行枚举,看一下是否还有其他的方法可以形成另一个字符串\(s_{1}\),然后如果重复,就输出应该输出的东西就好了
【\(code\)】:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <map>
#include <stack>
#define int unsigned long long
using namespace std ;
const int kmaxn = 2e7 + 10 ;
const int p1 = 1e3 + 7 ;
const int p2 = 1331 ;
const int mod1 = 10003 ;
const int mod2 = 13331 ;
inline int read()
{
int x = 0 , f = 1 ; char ch = getchar() ;
while(!isdigit(ch)) { if(ch == '-') f = - 1 ; ch = getchar() ; }
while( isdigit(ch)) { x = x * 10 + ch - '0' ; ch = getchar() ; }
return x * f ;
}
int n , x , y , ans , tot , p;
bool flag ;
char s[kmaxn] ;
int sum[kmaxn];
int power[kmaxn] ;
void prepare() //预处理 ,艹,这是板子。
{
power[0] = 1 ;
for(int i = 1 ; i <= n ; i++) //预处理出p2的n次方
{
power[i] = power[i - 1] * p2 ; //直接让其无符号整数自动截取来完成取模,省去耗时间复杂度较高的模运算
}
for(int i = 1 ; i <= n ; i++) //预处理出字符串的哈希值
{
sum[i] = sum[i - 1] * p2 + s[i] - 'A' + 1 ;
}
}
void work()
{
for(int i = 1 ; i <= n ; i++)
{
if(i <= n/2)
{
x = sum[n / 2 + 1] - sum[i] * power[n / 2 - i + 1] + sum[i - 1] * power[n / 2 - i + 1];
}
else
{
x = sum[n / 2] ;
}
if(i <= n /2 + 1)
{
y = sum[n] - sum[n / 2 + 1] * power[n / 2] ;
}
else
{
y = (sum[i - 1] - sum[n / 2] * power[i - n / 2 - 1]) * power[n - i] + sum[n] - sum[i] * power[n - i] ;
}
if(x == y)
{
if(flag && ans != x) //形成的字符串不唯一
{
printf("NOT UNIQUE\n") ;
return ;
}
flag = 1 , ans = x , p = i ;
}
}
if(!flag)
{
printf("NOT POSSIBLE\n") ;
return ;
}
for(int i = 1 ; tot < n / 2 ; i ++)
{
if(i != p )
{
printf("%c" , s[i]) ;
tot ++ ;
}
}
printf("\n") ;
return ;
}
signed main()
{
n = read() ;
scanf("%s" , s+1) ;
if(n % 2 == 0)
{
printf("NOT POSSIBLE\n") ;
return 0 ;
} //如果是偶数, 那么删去一个字符会形成奇数 ,但是 copy的必是偶数 , 故 不可能有解
prepare() ;
work() ;
return 0 ;
}