第2部分 字符串算法(提高篇)--第1章 哈希和哈希表-1459:friends

1459:friends

时间限制: 1000 ms 内存限制: 65536 KB
提交数: 1379 通过数: 258
【题目描述】
原题来自:BalticOI 2014

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

【输入】
第一行一个数 N,表示 U 的长度。 第二行一个字符串 U,保证 U 由大写字母组成。

【输出】
输出一行,若 S 不存在,输出 NOT POSSIBLE。若 S 不唯一,输出 NOT UNIQUE,否则输出 S。

【输入样例】
7
ABXCABC
【输出样例】
ABC
【提示】
样例输入2:

6
ABCDEF
样例输出2:

NOT POSSIBLE
样例输入3:

9
ABABABABA
样例输出3:

NOT UNIQUE
数据范围:

2≤N≤2000001.


思路:利用哈希来做。
输入的字符串的长度是S的二倍+1,所以如果长度是偶数,直接输出NOT POSSIBLE即可。题意得S最多就两种。然后我们可以遍历整个字符串,遍历到第i位时便认为这一位即新加的,因为我们要求的字符串的长度是固定的的,所以只要我们确定了最后一步加上的是哪一位,我们求出删去这一位之后字符串的前一半的哈希值和后一半的哈希值作比较就可以得到要求的字符串。
网上的哈希查询模板介绍:
查询子串hash值
必备的入门操作,因为OI中用到的hash一般都是进制哈希,因为它有一些极其方便的性质,比如说,是具有和前缀和差不多的性质的。
假设一个字符串的前缀hash值记为h[i],我们hash时使用的进制数为base,那么显然h[i]=h[i-1]*base+s[i]。
记p[i]表示base的i次方,那么我们可以通过这种方式O(1)得到一个子串的hash值(设这个子串为s[l]…s[r])

typedef unsigned long long ull;
ull get_hash(int l, int r) {
    return h[r] - h[l - 1] * p[r - l + 1];
}

可是为什么呢?
我们知道,进行进制哈希的过程本质上就是把原先得到的哈希值在base进制上强行左移一位,然后放进去当前的这个字符。
现在的目的是,取出l到r这段子串的hash值,也就是说,h[l−1]这一段是没有用的,我们把在h[r]这一位上,h[l-1]这堆字符串的hash值做的左移运算全部还原给h[l-1],就可以知道h[l-1]在h[r]中的hash值,那么减去即可。(简单的容斥思想)
这是基本操作,现在来看一个这个的拓展问题。
题意:
现在有一个字符串s,每次询问它的一个子串删除其中一个字符后的hash值(删除的字符时给定的)
要求必须O(1)回答询问
删除操作?那不能像上面那样子简单粗暴的来搞了,但是其实本质上是一样的。
假设我们现在询问的区间为[l,r],删除的字符为x(指位置,不是字符)

类比上面的做法,我们可以先O(1)得到区间[l,x−1]和区间[x+1,r]的hash值,那么现在要做的事情就是把这两段拼起来了,由于我们使用的是进制hash,所以其实很简单,强行将前面的区间强行左移r-x位(这么看可能会好理解一点:r-(x+1)+1就好。
代码实现

typedef unsigned long long ull;
ull get_hash(int l, int r) {
    return h[r] - h[l - 1] * p[r - l + 1];
}
ull get_s(int l, int r, int x) {
    return get_hash(l, x - 1) * p[r - x] + get_hash(x + 1, r);
}

这题的原题是LOJ#2823. 「BalticOI 2014 Day 1」三个朋友 ,需要分类讨论一下。

#include
#include
#include
#include
const int maxn = 2e6 + 10;
typedef unsigned long long ull;
#define base 131
ull h[maxn], p[maxn];
ull get_hash(int l, int r)
{
    return h[r] - h[l - 1] * p[r - l + 1];
}
ull get_s(int l, int r, int x)
{
    return get_hash(l, x - 1) * p[r - x] + get_hash(x + 1, r);
}//套用模板
int main()
{
    int n, mid; string s;
 
    cin >> n >> s;
    if (!(n & 1)) {
        cout << "NOT POSSIBLE\n"; return 0;
    }
    p[0] = 1;
    for (int i = 1; i <= maxn; i++) p[i] = p[i - 1] * base;
    s = " " + s;//因为我们求get_s的时候会遇到x-1,所以下标如果从0开始会RE,因此我们给s的第一位变成空格
    h[1] = s[1];
    for (int i = 2; i <= n; i++)
        h[i] = h[i - 1] * base + s[i];
    mid = n / 2 + 1;
    ull a, b, ans = 0; b = get_hash(mid + 1, n);
    //这里我是分开处理了,前一半和后一半分开
    //因为如果你假设的是前一半中删去一个,那么后半部分字符串就是我们要求的,它的哈希值是固定的,这样只用算一次
    string Ans, ANS1, ANS2;
    for (int i = mid + 1; i <= n; i++) Ans += s[i];//把字符串后半部分存储起来
    for (int i = 1; i < mid; i++) {
        a = get_s(1, mid, i);
        // cout << i << " " << a << " " << b << endl;
        if (a == b) {
            ans++, ANS1 = Ans; break;//如果哈希值相等,说明Ans是答案之一
        }
    }
    Ans = "";
    for (int i = 1; i <= mid - 1; i++) Ans += s[i];
    b = get_hash(1, mid - 1);
    for (int i = mid; i <= n; i++) {
        a = get_s(mid, n, i);
        // cout << i << " " << a << " " << b << endl;
        if (a == b) {
            ans++, ANS2 = Ans; break;
        }
    }//这一部分是从后一半中删去一个字符,跟上面那种情况同样的做法
    if (ans == 0) cout << "NOT POSSIBLE\n";//如果ans还是0,说明没有出现满足条件的
    else if (ans == 1 || ANS1 == ANS2) cout << (ANS1 == "" ? ANS2 : ANS1) << endl;//如果只有1种情况,输出那一种。但是还有一种情况就是,分开的两种都有结果但是结果相同,所以我们也输出一个。
    else cout << "NOT UNIQUE\n";//否则就是有两个解
}

你可能感兴趣的:(信息学C++,一本通)