《信息学奥赛一本通·高手专项训练》集训 Day 8

哈希

100+97+0=197/Rank 8\color{Green}100\color{Black}+\color{#92E411}97\color{Black}+\color{Red}0\color{Black}=\color{#92E411}197\color{Black}/\text{Rank 8}

A. 不同子串\color{#F39C11}\text{A. 不同子串}

题目

统计一个长度为 nn 的只包含小写字母的字符串有多少个不同的长度为 mm 的子串。

题解

先求整个字符串的 Hash\text{Hash},然后求出每个长度为 mm 的子串的 Hash\text{Hash},把第一次出现的子串计入答案即可。

本题数据较强,要使用双 Hash\text{Hash}

代码

#include <bits/stdc++.h>
#define ll long long

#define ull unsigned long long

using namespace std;
long long read() {
    long long x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
void write(long long x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
const int N = 2e5 + 10;
int n, m, ans = 1;
string s;
ll P1 = 13331, P2 = 131, P3 = 1e9 + 7, P4 = 998244353;
ll a1, a2, a3, a4, b[N];
map<ll, bool> h1, h2, h3, h4;
ll qmi(int a, ll p, ll mod) {
    ll ans = 1;
    while (a) {
        if (a & 1)
            ans = ans * p % mod;
        p = p * p % mod;
        a >>= 1;
    }
    return ans;
}
int main() {
    freopen("string.in", "r", stdin);
    freopen("string.out", "w", stdout);
    n = read();
    m = read();
    cin >> s;
    for (int i = 0; i < m; i++) {
        a1 = (a1 * P1 % P3 + (s[i] - 'a')) % P3;
        a2 = (a2 * P2 % P4 + (s[i] - 'a')) % P4;
    }
    h1[a1] = h2[a2] = 1;
    for (int i = 1; i + m - 1 < n; i++) {
        a1 = ((a1 - ((ll)(s[i - 1] - 'a')) % P3 * qmi(m - 1, P1, P3) % P3) % P3 + P3) % P3;
        a2 = ((a2 - ((ll)(s[i - 1] - 'a')) % P4 * qmi(m - 1, P2, P4) % P4) % P4 + P4) % P4;
        a1 = (a1 * P1 % P3 + (s[i + m - 1] - 'a')) % P3;
        a2 = (a2 * P2 % P4 + (s[i + m - 1] - 'a')) % P4;
        if (h1[a1] && h2[a2])
            continue;
        h1[a1] = h2[a2] = 1;
        ans++;
    }
    write(ans);
    return 0;
}

B. 还原字符串\color{#FFC116}\text{B. 还原字符串}

题目

有一个字符串 SS ,先令 T=S+ST=S+S

选择一个非负整数 xx ,一个字符 cc ,令 U=T1+T2++Tx1+c+Tx+Tx+1++TTU=T_1+T_2+…+T_{x-1}+c+T_x+T_{x+1}+…+T_{|T|}

现在给定 UU ,还原 SS

题解

枚举 cc 的位置,我们可以预处理出 UUHash\text{Hash} 值,求出相应的子串 SS 判断是否相等即可。

记得分类讨论。

如果我们把 cc 删去后,如何计算相应的 T,ST,S 呢,以 xn2=midx\le\frac{n}{2}=mid 时为例,把字符串都当做 PP 进制数表示,设 U=ABCDEFG,x=2U=\texttt{ABCDEFG},x=2sumisum_i 表示 UUii 位所组成的 PP 进制数,则删去后的 TTsumnsumi×Pni+sumi1×Pnisum_n-sum_i\times P^{n-i}+sum_{i-1}\times P^{n-i},转换过程为:ABCDEFG00CDEFG0ACDEFG\texttt{ABCDEFG}\rightarrow \texttt{00CDEFG}\rightarrow \texttt{0ACDEFG}TT 还满足 T=S+S=(sumnsumn/2+1×Pn/2)×(1+Pn/2)T=S+S=(sum_n-sum_{n/2+1}\times P^{n/2})\times(1+P^{n/2}),即 ABCDEFG0000EFG0EFGEFG\texttt{ABCDEFG}\rightarrow \texttt{0000EFG}\rightarrow \texttt{0EFGEFG}

注意一定不要在代码里写上这个:unsigned long long mp[N]={1};,不然会 CE\text{CE}

代码

#include <bits/stdc++.h>
#define ll long long

#define ull unsigned long long

using namespace std;
long long read() {
    long long x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
void write(long long x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
const int N = 2e6 + 10;
int n, pos;
ull sum[N], p = 27, mp[N], s1, s2, ans;
string s, t;
int main() {
    freopen("reduction.in", "r", stdin);
    freopen("reduction.out", "w", stdout);
    n = read();
    if (n % 2 == 0) {
        puts("NOT POSSIBLE");
        return 0;
    }
    cin >> s;
    mp[0] = 1;
    for (int i = 1; i <= n; i++) {
        mp[i] = mp[i - 1] * p;
        sum[i] = sum[i - 1] * p + s[i - 1] - 'A' + 1;
    }
    for (int i = 1; i <= n / 2; i++) {
        s1 = sum[n] - sum[i] * mp[n - i] + sum[i - 1] * mp[n - i];
        s2 = (sum[n] - sum[n / 2 + 1] * mp[n / 2]) * (1 + mp[n / 2]);
        if (s1 == s2) {
            if (pos == 0) {
                pos = i;
                ans = s1;
            } else if (ans != s1) {
                puts("NOT UNIQUE");
                return 0;
            }
        }
    }
    for (int i = n / 2 + 1; i <= n; i++) {
        s1 = sum[n] - sum[i] * mp[n - i] + sum[i - 1] * mp[n - i];
        s2 = sum[n / 2] * (1 + mp[n / 2]);
        if (s1 == s2) {
            if (pos == 0) {
                pos = i;
                ans = s1;
            } else if (ans != s1) {
                puts("NOT UNIQUE");
                return 0;
            }
        }
    }
    if (ans == 0) {
        puts("NOT POSSIBLE");
        return 0;
    }
    for (int i = 1; i <= n / 2 + (pos <= n / 2 + 1); i++) {
        if (pos != i)
            cout << s[i - 1];
    }
    return 0;
}

C. 对称之美\color{#9D3DCF}\text{C. 对称之美}

题目

给定 TT 个多边形,求这 TT 个多边形分别有多少条对称轴。

题解

根据初中学过的全等相关知识,我们知道如果两个多边形对应边相等,对应角相等,那么这两个多边形全等。对称显然也有类似的性质。

于是,我们可以用某种方法求出多边形边和角的表示方法,把他们按边 \rightarrow\rightarrow\rightarrow 角的方式按顺序排成一列,并复制一遍在末尾,这样如果从位置 xx 断开(xx 可能是边或角,角的断开相当于经过顶点的对称轴),那么对称轴分开的两个多边形就可以用 [x+1,x+len][x+1,x+len][x+len+2,x+2×len+1][x+len+2,x+2\times len+1] 表示,分别从前往后和从后往前 Hash\text{Hash} 再比较即可。

对于边的表示,我们可以使用不开方的勾股定理,对于角的表示,我们可以使用叉积,设所求角由两条射线 AB,ACAB,AC 组成,则叉积 v=(xAxC)(xBxA)(yAyC)(yByA)v=(x_A-x_C)(x_B-x_A)-(y_A-y_C)(y_B-y_A)

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;
long long read() {
    long long x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
void write(long long x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
const int N = 4e5 + 10;
const ll mod = 998244353;
int t, n, ans;
ll x[N], y[N], hz[N], hf[N], e[N], P = 13331, mp[N] = { 1 };
void jiao(int p, int u) {
    int q = p + 1, r = p + 2;
    if (q > n)
        q -= n;
    if (r > n)
        r -= n;
    ll a = x[q] - x[p], b = y[q] - y[p];
    ll c = x[r] - x[q], d = y[r] - y[q];
    e[u] = a * d - b * c;
}
void bian(int p, int u) {
    int q = p + 1;
    if (q > n)
        q -= n;
    ll a = x[p] - x[q], b = y[p] - y[q];
    e[u] = a * a + b * b;
}
ll calcz(int l, int r) { return hz[r] - hz[l - 1] * mp[r - l + 1] % mod; }
ll calcf(int l, int r) { return hf[l] - hf[r + 1] * mp[r - l + 1] % mod; }
bool check(int p, int len) {
    ll a = (calcz(p + 1, p + len) % mod + mod) % mod;
    ll b = (calcf(p + len + 2, p + 2 * len + 1) % mod + mod) % mod;
    return a == b;
}
int main() {
    freopen("beauty.in", "r", stdin);
    freopen("beauty.out", "w", stdout);
    for (int i = 1; i <= 4e5; i++) mp[i] = mp[i - 1] * P % mod;
    t = read();
    while (t--) {
        n = read();
        ans = 0;
        for (int i = 1; i <= n; i++) {
            x[i] = read();
            y[i] = read();
        }
        for (int i = 1; i <= n; i++) jiao(i, i * 2);
        for (int i = 1; i <= n; i++) bian(i, i * 2 - 1);
        for (int i = 1; i <= n * 2; i++) e[i + n * 2] = e[i];
        for (int i = 1; i <= n * 4; i++) hz[i] = (hz[i - 1] * P % mod + e[i]) % mod;
        hf[n * 4] = e[n * 4];
        for (int i = n * 4 - 1; i >= 1; i--) hf[i] = (hf[i + 1] * P % mod + e[i]) % mod;
        for (int i = 1; i <= n; i++) ans += check(i, n - 1);
        write(ans);
        putchar('\n');
    }
    return 0;
}

KMP算法

100+100+100=300/Rank 1\color{Green}100\color{Black}+\color{Green}100\color{Black}+\color{Green}100\color{Black}=\color{Green}300\color{Black}/\color{Gold}\text{Rank 1}

A. 偶串之和\color{#FFC116}\text{A. 偶串之和}

题目

给你一个字符串,求所有长度为偶数的前缀在整个字符串出现的次数和。

题解

先进行 KMP\text{KMP} 的前缀匹配,设 fif_i 表示以 ii 结尾的字符串中出现的长度为偶数的整个字符串前缀的个数,显然若 ii 是个长度为偶数的前缀的右端点,则 fi1f_i\leftarrow 1,另外,nextinext_i 中出现的长度为偶数的整个字符串前缀显然也在 ii 中出现,所以还有 fifnextif_i\leftarrow f_{next_i},对于 nextnextinext_{next_i} 一类的答案,已被包括在 nextinext_i 中,不必计算。

最后对所有 fif_i 求和就是答案。

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;
long long read() {
    long long x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
void write(long long x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
const int N = 1e6 + 10;
string s;
int f[N];
ll ans, a1;
struct KMP {
    int nxt[N];
    void qnext(string a) {
        int l1 = a.size();
        for (int i = 1, j; i < l1; i++) {
            j = nxt[i];
            if (i & 1)
                f[i + 1]++;
            while (j && a[j] ^ a[i]) j = nxt[j];
            nxt[i + 1] = (a[i] ^ a[j] ? 0 : j + 1);
            f[i + 1] += f[j + 1];
        }
    }
} kmp;
int main() {
    freopen("prefix.in", "r", stdin);
    freopen("prefix.out", "w", stdout);
    cin >> s;
    kmp.qnext(s);
    for (int i = 0; i <= s.size(); i++)
        ans += f[i + 1];
    write(ans);
    return 0;
}

B. 密文翻译\color{#FFC116}\text{B. 密文翻译}

题目

给出一段字母,它的前半段由密文组成,后半段由明文的某一个前缀(可以为空)组成,现在你有一个加密字母表格,让你还原完整的内容(也就是密文+明文的形式)。 如果有多种可能,我们认为那个最短的信息是完整的内容。

题解

把原字符串 SS 解密得到 TT,对 T+#*#+ST+\texttt{\#*\#}+S 进行 KMP\text{KMP} 前缀匹配,得到结尾匹配的最大长度,这样完整内容就只要加上相应最少的字符即可。

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;
long long read() {
    long long x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
void write(long long x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
const int N = 1e6 + 10;
string s, s_, t, str;
int p;
char c[N];
struct KMP {
    int nxt[N];
    void qnext(string a) {
        int l1 = a.size();
        for (int i = 1, j; i < l1; i++) {
            j = nxt[i];
            while (j && a[j] ^ a[i]) j = nxt[j];
            nxt[i + 1] = (a[i] ^ a[j] ? 0 : j + 1);
        }
    }
} kmp;
int main() {
    freopen("recover.in", "r", stdin);
    freopen("recover.out", "w", stdout);
    cin >> s_ >> s;
    for (int i = 0; i < s_.size(); i++) c[s_[i] - 'a'] = i + 'a';
    for (int i = 0; i < s.size(); i++) t += c[s[i] - 'a'];
    str = t + "#*#" + s;
    kmp.qnext(str);
    p = kmp.nxt[str.size()];
    while (p > s.size() / 2) p = kmp.nxt[p];
    cout << s;
    for (int i = p; i < t.size() - p; i++) cout << t[i];
    return 0;
}

C. 撰写博客\color{#9D3DCF}\text{C. 撰写博客}

题目

小明发了一篇博客,由 nn 个小写英文字母组成。

但是写完这篇博客之后,小明发现博客由于包含一些词语,被自动隐藏了。

具体地,这些词语有 mm 个,分别为 T1,T2,,TmT_1,T_2,…,T_m

小明发现,只要包含了其中某个词语,博客就会被自动隐藏。换言之,对于任意 1im1\le i\le mTiT_i 都不能是最终发表的博客 SS 的子串。

于是小明决定在原来的博客 SS 上把一部分字母替换成空格,使得它不再包含任何词语。

小明发现如果她把第 ii 个字母替换成空格,那么整篇博客的价值会减少 aia_i

小明想要知道,如何替换可以得到一篇不会被自动隐藏的博客,而价值的减少量最少。

值得一提的是,如果小明把一个字母替换成空格,那么与之相邻的两个字母也不会变得连续。

但是小明请你帮他求出价值的最少减少量。

题解

处理出所有字符串的 Hash\text{Hash} 值,再求出 preipre_i 表示只要 [prei,i][pre_i,i] 之间改掉一个字母,所有包括 ii 的单词都会失效,设 dpidp_i 表示把第 ii 个字母换掉后最少减少的值,那么有:

dpi=minj=prei1i1{dpj}+aidp_i=\min_{j=pre_{i-1}}^{i-1}\{dp_j\}+a_i

用单调队列优化即可。

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;
long long read() {
    long long x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
void write(long long x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
const int N = 2e5 + 10;
int n, m, a[N], q[N], l = 1, r = 1;
string s, t[15];
int pre[N];
ll mod = 998244353, p = 13331, mp[N], has[N], hasht[N], dp[N];
ll calc(ll l, ll r) { return ((has[r] - has[l - 1] * mp[r - l + 1] % mod) % mod + mod) % mod; }
int main() {
    freopen("blog.in", "r", stdin);
    freopen("blog.out", "w", stdout);
    n = read();
    m = read();
    cin >> s;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= m; i++) cin >> t[i];
    mp[0] = 1;
    for (int i = 1; i <= n; i++) mp[i] = mp[i - 1] * p % mod;
    for (int i = 1; i <= n; i++) has[i] = (has[i - 1] * p % mod + s[i - 1] - 'a') % mod;
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= t[i].size(); j++) {
            hasht[i] = (hasht[i] * p % mod + t[i][j - 1] - 'a') % mod;
        }
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (i + t[j].size() - 1 > n + 1)
                continue;
            if (calc(i, i + t[j].size() - 1) == hasht[j])
                pre[i + t[j].size() - 1] = max(pre[i + t[j].size() - 1], i);
        }
    }
    for (int i = 1; i <= n + 1; i++) pre[i] = max(pre[i], pre[i - 1]);
    for (int i = 1; i <= n + 1; i++) {
        while (l <= r && q[l] < pre[i - 1]) l++;
        dp[i] = dp[q[l]] + a[i];
        while (l <= r && dp[q[r]] >= dp[i]) r--;
        q[++r] = i;
    }
    write(dp[n + 1]);
    return 0;
}
posted @ 2022-08-08 19:53  luckydrawbox  阅读(72)  评论(0)    收藏  举报  来源