test20230304考试总结(2023春 · 字符串)
前言
赛时得分明细:
A | B | C | D | Total | Rank |
---|---|---|---|---|---|
100 | 100 | 0 | 70 | 270 | 2 |
C题如此说道:字符串没有学好的报应!!
A. P4391 [BOI2009]Radio Transmission 无线传输
题面
给定一个字符串 \(s_1\),它是由某个字符串 \(s_2\) 不断自我连接形成的(保证至少重复 \(2\) 次)。但是字符串 \(s_2\) 是不确定的,现在只想知道它的最短长度是多少。
\(L\) 为 \(s_1\) 的长度,所有数据点满足 \(1 < L \le 10^6\)
题解
答案为 \(n - fail(n)\),其中 \(fail\) 为 \(s_1\) 的失配函数。
证明:
如图,假设这两段是整个字符串ss的最大公共前后缀,我将前缀和后缀分开,令它们上下一一对应;
所以推出:
-
因为上下对应相等,故第1段等于红色段;
-
因为是公共前后缀,故第2段等于第1段;
-
因为上下对应相等,故第3段等于第2段;
-
因为是公共前后缀,故第4段等于第3段;
-
......
-
红色段就是循环子串;
红色段的长度即为 \(n - fail(n)\),征毕。
代码
#include <bits/stdc++.h>
#define int long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
using namespace std;
inline int read() {
rint x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
void print(int x){
if(x<0){putchar('-');x=-x;}
if(x>9){print(x/10);putchar(x%10+'0');}
else putchar(x+'0');
return;
}
const int N = 1e6 + 10;
int n, fail[N];
char s[N];
signed main() {
n = read();
cin >> s + 1;
for (int i = 2, j = 0; i <= n; i++) {
while(j && s[i] != s[j + 1]) j = fail[j];
if(s[i] == s[j + 1]) j++;
fail[i] = j;
}
cout << n - fail[n] << '\n';
return 0;
}
B. P4824 [USACO15FEB] Censoring S
题面
题解
代码
C. P4503 [CTSC2014] 企鹅 QQ
题面
定义若两个账户名称是相似的,当且仅当这两个字符串等长且恰好只有一位不同。例如“Penguin1”和“Penguin2”是相似的,但“Penguin1”和“2Penguin”不是相似的。求在给定的 \(n\) 个账户名称中,有多少对是相似的。
题解
组合数学 + Hash
预处理出每一个字符串的前后缀 \(Hash\),再枚举每一位,用组合数学统计合法数对就行。
代码
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define H 27
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007
using namespace std;
inline int read() {
rint x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
void print(int x){
if(x<0){putchar('-');x=-x;}
if(x>9){print(x/10);putchar(x%10+'0');}
else putchar(x+'0');
return;
}
const int N = 3e4 + 10, M = 205;
int n, L, S;
ull pre[N][M], nxt[N][M], p[N], ans;
char s[N][M];
pair<ull, ull> k[N];
signed main() {
n = read(), L = read(), S = read();
p[0] = 1;
For(i,1,L) p[i] = p[i - 1] * H;
For(i,1,n) {
For(j,1,L) cin >> s[i][j];
For(j,1,L) pre[i][j] = pre[i][j - 1] * H + s[i][j];
FOR(j,L,1) nxt[i][j] = nxt[i][j + 1] * H + s[i][j];
}
For(i,1,L) {
For(j,1,n) {
k[j].first = pre[j][i-1];
k[j].second = nxt[j][i+1];
}
sort(k + 1, k + n + 1);
int l = 1, r = 1;
while(r <= n) {
while(k[l] == k[r] && r <= n) r++;
r--;
ans += (((r - l + 1) * (r - l)) >> 1);
l = r + 1, r++;
}
}
cout << ans << '\n';
return 0;
}
D. P7469 [NOI Online 2021 提高组] 积木小赛
题面
给定两个长度为 \(n\) 的小写字母串 \(s\) 和 \(t\)。求在不同情况下从 \(s\) 中选出一个子序列与 \(t\) 中选出一个子串对应相同的方案数(两种情况不同,当且仅当两序列所选出的字符串在两种情况中不同)。
题解
枚举 \(t\) 的子串,固定左端点 \(L\),右端点 \(r\) 递增。同时在 \(s\) 中找是否有子序列与所枚举的字串对应相同。为了方便统计方案数,可以用 \(Hash\) 来判断两种情况是否相同。把 \(Hash\) 值丢到 \(unordered\)_\(set\),\(set\),或随便搞一个数组(之后进行 sort 和 unique)里面去。实测只有最后一个方法可以通过。
时间复杂度 \(O(N^2\log n)\)。
代码
#include <bits/stdc++.h>
#define int long long
#define H 37
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
using namespace std;
inline int read() {
rint x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
void print(int x){
if(x<0){putchar('-');x=-x;}
if(x>9){print(x/10);putchar(x%10+'0');}
else putchar(x+'0');
return;
}
const int N = 3e3 + 10;
const int M = 9e6 + 10;
int n, hs, nxt[N][156], res[M], tot;
char s[N], t[N];
signed main() {
n = read();
For(i,1,n) cin >> s[i];
For(i,1,n) cin >> t[i];
For(i,1,n) {
For(j,i,n) {
if(!nxt[i][s[j]]) nxt[i][s[j]] = j;
}
}
For(i,1,n) {
hs = 0;
int k = 1;
For(j,i,n) {
k = nxt[k][t[j]];
if(!k) break;
hs = 1ll * (hs * H) + (t[j] - 'a' + 1);
res[++tot] = hs;
k++;
}
}
sort(res + 1, res + 1 + tot);
cout << ((unique(res + 1, res + 1 + tot)) - res - 1) << '\n';
return 0;
}