《信息学奥赛一本通·高手专项训练》集训 Day 8
哈希
题目
统计一个长度为 的只包含小写字母的字符串有多少个不同的长度为 的子串。
题解
先求整个字符串的 ,然后求出每个长度为 的子串的 ,把第一次出现的子串计入答案即可。
本题数据较强,要使用双 。
代码
#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;
}
题目
有一个字符串 ,先令 。
选择一个非负整数 ,一个字符 ,令 。
现在给定 ,还原 。
题解
枚举 的位置,我们可以预处理出 的 值,求出相应的子串 判断是否相等即可。
记得分类讨论。
如果我们把 删去后,如何计算相应的 呢,以 时为例,把字符串都当做 进制数表示,设 , 表示 前 位所组成的 进制数,则删去后的 为 ,转换过程为:。 还满足 ,即 。
注意一定不要在代码里写上这个:unsigned long long mp[N]={1};,不然会 。
代码
#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;
}
题目
给定 个多边形,求这 个多边形分别有多少条对称轴。
题解
根据初中学过的全等相关知识,我们知道如果两个多边形对应边相等,对应角相等,那么这两个多边形全等。对称显然也有类似的性质。
于是,我们可以用某种方法求出多边形边和角的表示方法,把他们按边 角 边 角的方式按顺序排成一列,并复制一遍在末尾,这样如果从位置 断开( 可能是边或角,角的断开相当于经过顶点的对称轴),那么对称轴分开的两个多边形就可以用 和 表示,分别从前往后和从后往前 再比较即可。
对于边的表示,我们可以使用不开方的勾股定理,对于角的表示,我们可以使用叉积,设所求角由两条射线 组成,则叉积 。
代码
#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算法
题目
给你一个字符串,求所有长度为偶数的前缀在整个字符串出现的次数和。
题解
先进行 的前缀匹配,设 表示以 结尾的字符串中出现的长度为偶数的整个字符串前缀的个数,显然若 是个长度为偶数的前缀的右端点,则 ,另外, 中出现的长度为偶数的整个字符串前缀显然也在 中出现,所以还有 ,对于 一类的答案,已被包括在 中,不必计算。
最后对所有 求和就是答案。
代码
#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;
}
题目
给出一段字母,它的前半段由密文组成,后半段由明文的某一个前缀(可以为空)组成,现在你有一个加密字母表格,让你还原完整的内容(也就是密文+明文的形式)。 如果有多种可能,我们认为那个最短的信息是完整的内容。
题解
把原字符串 解密得到 ,对 进行 前缀匹配,得到结尾匹配的最大长度,这样完整内容就只要加上相应最少的字符即可。
代码
#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;
}
题目
小明发了一篇博客,由 个小写英文字母组成。
但是写完这篇博客之后,小明发现博客由于包含一些词语,被自动隐藏了。
具体地,这些词语有 个,分别为 。
小明发现,只要包含了其中某个词语,博客就会被自动隐藏。换言之,对于任意 , 都不能是最终发表的博客 的子串。
于是小明决定在原来的博客 上把一部分字母替换成空格,使得它不再包含任何词语。
小明发现如果她把第 个字母替换成空格,那么整篇博客的价值会减少 。
小明想要知道,如何替换可以得到一篇不会被自动隐藏的博客,而价值的减少量最少。
值得一提的是,如果小明把一个字母替换成空格,那么与之相邻的两个字母也不会变得连续。
但是小明请你帮他求出价值的最少减少量。
题解
处理出所有字符串的 值,再求出 表示只要 之间改掉一个字母,所有包括 的单词都会失效,设 表示把第 个字母换掉后最少减少的值,那么有:
用单调队列优化即可。
代码
#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;
}

浙公网安备 33010602011771号