bzoj·[Baltic2014]friends

初见安~这里是传送门:bzoj P3916 friends

Description

有三个好朋友喜欢在一起玩游戏,A君写下一个字符串S,B君将其复制一遍得到T,C君在T的任意位置(包括首尾)插入一个字符得到U.
现在你得到了U,请你找出S.

Input

第一行一个数N,表示U的长度. 对于100%的数据 2<=N<=2000001
第二行一个字符串U,保证U由大写字母组成

Output

输出一行,若S不存在,输出"NOT POSSIBLE".若S不唯一,输出"NOT UNIQUE".否则输出S.

Sample Input

7
ABXCABC

Sample Output

ABC

Sol

这个题就是字符串Hash的一个比较模板的题目。

首先我知道——我们得到的字符串U是由S复制一遍,也就是形如“SS”,再插入一个字符得到的。所以如果U的长度是偶数,就一定无解。那么我们如果删掉那个插入的字符,再对半分开,是不是就可以还原S了?所以我们可以枚举插入的字符,每次都求一次删掉那个字符后整个字符串两边的Hash值,看看是否相等,相等则为答案。当然,若并非第一次得到答案,且答案得到的S不同,那么就是NOT UNIQUE了。整个的复杂度是O(strlen(U))的。

那么重点来了——怎么处理删掉枚举到的字符后的整个字符串并对半分开,求出各自的Hash值?

这里我们可以通过与处理先保证目前的字符串长度为奇数。很容易想到:一定是和整个字符串的最中间的位置有关的。所以我们分三部分来分别考虑:【Hash(i) = (Hash[i - 1] * p + string[i] - 'A'+1) modq

1.中点前的部分[x < mid]

【这里以圆圈代替字符】

可以发现,只要枚举的点在mid前面,那么mid就属于前半部分

设s1为前半部分的Hash值,s2为后半部分的Hash值。则由求任意子串Hash值得公式可得:s_2=(Hash[len] - Hash[mid] * p^{len - mid} mod q + q) modq【关于mod的操作是避免因为后者取模有负数出现】

而关于s1的话,我们可以把整个前半部分分为两个字符串,再拼合起来——一部分是[1,x),一部分是(x,mid]

前半部分也很好处理,单独的Hash值就是t_1 = Hash[x - 1];而后半部分我们也可以直接利用公式,也就是:

t_2 = (Hash[mid]-Hash[x] * p^{mid - x} mod q + q) mod q

那么这两部分如何拼起来呢?简单模拟一下求字符串Hash值时的递推过程,就可以发现:t1串到t2串的末尾这段过程中,整体的Hash值乘上了p的mid - i次方。所以可以直接合起来了:

\\s1 = ((Hash[mid] - Hash[x] * p^{mid - x} mod q + q) modq + Hash[x - 1] * p^{mid - x})modq

 

2.中点时[x == mid]

在中点位置时,很明显s2是没有变化的,s1 = Hash[x - 1],带入上述公式同样成立,所以可以直接略过。

 

3.中点后的部分[x > mid]

很明显,当x > mid的时候,mid就归属于后半部分了,可以直接得到的是s1 = Hash[mid - 1]

而s2相比之下就比较复杂了。我们还是用t1和t2来表示[mid, x)(x, len]两部分。这次两个部分要同时使用子串公式了,再加上维护需要的mod操作,代码看起来很长所以就自己去写吧……讲到这里就已经很清晰了。

上代码——

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define maxn 2000005
using namespace std;
typedef long long ll;
const int p = 131, q = 998244353;
int read() {
    int x = 0, f = 1, ch = getchar();
    while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
    while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
    return x * f;
}
 
int n;
ll sum[maxn], pwr[maxn], s1, s2, tmp;//一定要开long long!!! 
char s[maxn], ans[maxn];
int main() {
    n = read(); scanf("%s", s + 1);//后文求strlen都加了一是因为传过去的是开始的地址,我是从1开始的,所以地址也要+1 
    sum[0] = 0; register int len = strlen(s + 1), mid = (len >> 1) + 1;
    if(!(len & 1)) {puts("NOT POSSIBLE"); return 0;}//预判不合法 
    for(int i = 1; i <= len; i++)
        sum[i] = (sum[i - 1] * p + s[i] - 'A' + 1) % q;//求Hash值 
         
    pwr[0] = 1;
    for(int i = 1; i <= len + 1; i++) pwr[i] = (pwr[i - 1] * p) % q;//预处理出p的各个次幂的值 
 
    bool flag = false;
    for(int i = 1; i <= len; i++) {
        if(i <= mid) {//此处套公式 
            s1 = ((sum[mid] - sum[i] * pwr[mid - i] % q + q) % q + sum[i - 1] * pwr[mid - i]) % q;
            s2 = (sum[len] - sum[mid] * pwr[len - mid] % q + q) % q;
        }
        else {
            s1 = sum[mid - 1], //s2部分看起来很长,但是可以通过括号的位置来划分t1和t2所指的部分 
            s2 = ((sum[len] - sum[i] * pwr[len - i] % q + q) % q + (sum[i - 1] - sum[mid - 1] * pwr[i - mid] % q + q) * pwr[len - i] % q) % q;
        }
        if(s1 == s2) {//如果找到了一个原串S 
            if(!flag) {//如果这是第一次得到解,那么可以直接存储 
                tmp = s1;
                if(i <= mid) for(int j = mid + 1; j <= len; j++) ans[j - mid] = s[j];
                else for(int j = 1; j < mid; j++) ans[j] = s[j];
                flag = true;
            }
            else if(tmp != s1) {puts("NOT UNIQUE"); return 0;}//否则的话,除非解不相同才有多解,否则就不算多解【这个细节卡WA了好久 
        }
    }
     
    int t = strlen(ans + 1);
    if(!t) puts("NOT POSSIBLE");//没有得到过答案 
    else for(int i = 1; i <= t; i++) printf("%c", ans[i]);
    return 0;
}

迎评!!!:)

——End——

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值