T1. 不重最长子串
Link:点我跳转
题目概括:给定一个字符串 s s s,找出其中不含有重复字符的最长子串的长度。字符串由英文字母、数字和空格组成。长度在 0 ∼ 50000 0\sim 50000 0∼50000 之间
关于子串的定义,请出门右转到百度百科
一道水题,但还是有亿些技巧。
技巧:loc[i]
标记
i
i
i 出现的位置,求最长 / 不重复问题时会使用到。具体到这一题,loc[i]
表示第
i
i
i 个字符上一次出现的位置。
记录每一个字符之前相同字符出现的位置,否则初始位置全部为 − 1 -1 −1,也可以直接计算,每一次计算可以形成最长的序列长度然后更新。
核心思想:先定后配。即枚举一个端点,匹配另一个端点。具体到这一题,是枚举右端点,匹配左端点。
需要注意的是这个大坑:字符串也可以由空格组成。所以一定要用 getline
。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 5e4 + 9;
int ans, pos[N], loc[130];
string s;
int main() {
getline(cin, s);
memset(loc, -1, sizeof loc);
for (int i = 0; i < s.size(); i++)
pos[i] = loc[s[i]], loc[s[i]] = i;
for (int i = 0, j = 0; j < s.size(); j++) { // i-left, j-right
if (pos[j] >= i) i = pos[j] + 1;
ans = j-i+1 > ans ? j-i+1 : ans;
}
printf("%d", ans);
return 0;
}
还有这种写法,是比较好想到的,就不做过多的解释,看注释即可。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 130;
int loc[N], l, mx; // l-左端点
int main() {
string s;
getline(cin, s);
memset(loc, -1, sizeof loc);
for (int i = 0; i < s.size(); i++) {
if (loc[s[i]] >= l) { // 出现了两次
l = loc[s[i]] + 1;
} else {
mx = max(mx, i-l+1); // 更新最大值
}
loc[s[i]] = i; // 更新上一次出现的位置
}
cout << mx;
return 0;
}
T2. 小J的加密算法
Link:点我跳转
题目概括:输入一个字符串,将其按照 Z
字形重新排列,输出排列后的样子。例如:有字符串 LSRDCYJDL
,Z
字形的行数为
3
3
3,该字符串应该长这样:
L C L
S D Y D
R J
输出应该长这样(按行输出):
LCLSDYDRJ
(关于这个字符串的意思,我不敢多说,有知道的评论区见)
水题,找规律即可。
#include <iostream>
using namespace std;
const int N = 1009;
int col, c;
char m[N][N];
string s;
int main() {
cin >> s >> c;
if (c == 1) {
cout << s;
return 0;
}
for (int i = 0, r = 0; i < s.size(); i++) {
if (col%(c-1) == 0) {
m[r++][col] = s[i];
if (r == c)
r = c - 2, col++;
} else {
m[r--][col++] = s[i];
}
}
for (int i = 0; i < c; i++)
for (int j = 0; j <= col; j++)
if (m[i][j])
putchar(m[i][j]);
return 0;
}
T3. 数列的个数
Link:点我跳转
题目概括:给出两个整数 n , m n, m n,m。问有多少个长度为 n n n 的序列 A A A,满足以下条件:
- 1 ≤ A i ≤ m ( i = 1 , 2 , … , n ) 1 ≤ A_i ≤ m(i = 1, 2,\dots, n) 1≤Ai≤m(i=1,2,…,n)
- ∀ i ∈ [ 1 , n − 1 ] \forall i\in [1, n-1] ∀i∈[1,n−1], A i + 1 A_{i+1} Ai+1 是 A i A_i Ai 的倍数。
由于答案可能很大,所以你只需要输出答案对 998244353 998244353 998244353 取模的结果。
比较难,放洛谷应该是绿题的水平。
本题的核心思想仍然是“先定后配”。
分析过程如下:
关键词:倍数
因为 A i + 1 A_{i+1} Ai+1 是 A i A_i Ai 的倍数,所以 A i A_i Ai 一定是 A i + 1 A_{i+1} Ai+1 的约数。故这题会用到约数分解。
找规律
n
=
3
,
m
=
4
:
n=3, m=4:
n=3,m=4:
枚举所有情况:
如果从第一个数开始向后推,并不方便发现规律。不妨先定下最后一个数,往前推前面的数。该思想称为:正难则反。
例如,以
4
4
4 为结尾的数列:
归结起来,就是:
{
x
⟶
×
2
a
y
⟶
×
2
b
4
a
+
b
≤
2
\begin{cases} x\stackrel{\times 2^{a}}{\longrightarrow}y\stackrel{\times 2^{b}}{\longrightarrow}4\\ a + b \leq 2 \end{cases}
⎩⎨⎧x⟶×2ay⟶×2b4a+b≤2
所以,欲求以
4
4
4 结尾的数列的方案数,即是求
a
+
b
≤
2
a+b\leq2
a+b≤2 有多少种可能。
方便起见,把
≤
\leq
≤ 换成
=
=
=,我们再设一个未知数
c
c
c
a
+
b
+
c
=
2
a+b+c=2
a+b+c=2
这样,我们就把这个问题转换为:有
n
n
n 个自然数相加的和为
k
k
k,解有多少种。
分析这个问题,可以用“隔板法”。注意:此处的隔板法是允许将多个板子插在一个空中的。
有 C n − 1 + k k C^{k}_{n-1+k} Cn−1+kk 种方法。但是这种方法时间复杂度巨大,所以需要借助背包求解。
令 f[i][j]
表示前
i
i
i 个数用掉
j
j
j 个因子后的情况数。
f[i][j] = f[i-1][j] + f[i-1][j-1] + f[i-1][j-2] + ... + f[i-1][0]
= f[i-1][j] + f[i][j-1] // 完全背包
降维后:f[j] = f[j] + f[j-1]
即 f[j] += f[j-1]
。
这样就将组合数预处理出来了。
呼,终于到了上代码时间。码字码了几个小时了。
#include <iostream>
const int M = 998244353;
int f[20] = {1}, n, m;
int solve(int k) {
int ans = 1;
for (int i = 2; i*i <= k; ++i) {
if (k % i) continue;
int c = 0;
while (k%i == 0) k /= i, ++c;
ans = ans * 1LL * f[c] % M;
}
if (k > 1) ans = ans * 1LL * f[1] % M; // 质数情况
return ans;
}
int main() {
int ans = 0;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= 19; ++j)
(f[j] += f[j-1]) %= M;
for (int i = 1; i <= m; ++i)
(ans += solve(i)) %= M;
printf("%d", ans);
return 0;
}
T4. 匹配正则表达式
Link:点我跳转
先抖个机灵。C++ 头文件里内带正则表达式 regex
库,所以只需要调用库函数 regex_match
即可。
关于 regex
库的更多用法,请参考这篇博客
AC 代码:
#include <iostream>
#include <regex>
using namespace std;
string s, p;
int main() {
while (cin >> s >> p) {
int ans = regex_match(s, regex(p));
if (ans) {
puts("Yes");
} else {
puts("No");
}
}
return 0;
}
但这题显然不是考察我们的函数储备量,所以还要介绍下面这个方法。
本题的思路与 最长公共子序列(LCS) 比较相似。f[i][j]
表示
s
s
s 的前
i
i
i 个字符与
p
p
p 中的前
j
j
j 个字符是否能够匹配。在进行状态转移时,我们考虑
p
p
p 的第
j
j
j 个字符的匹配情况。
p[j]
是一个小写字母 a-z
,则 s[i]
必须也为同样的小写字母方能完成匹配。
当 p[j] = '.'
时,p[j]
与 s[i]
一定能匹配成功;
当 p[j] = '*'
时,表示可对 p[j]
的前一个字符 p[j−1]
匹配(或理解为复制)任意次(包括
0
0
0 次)。
d
p
[
i
]
[
j
]
=
{
i
f
s
[
i
]
=
p
[
j
]
:
d
p
[
i
−
1
]
[
j
−
1
]
i
f
p
[
j
]
=
′
∗
′
&
s
[
i
]
≠
p
[
j
−
1
]
:
d
p
[
i
]
[
j
−
2
]
i
f
p
[
j
]
=
′
∗
′
&
s
[
i
]
=
p
[
j
−
1
]
:
d
p
[
i
]
[
j
−
2
]
o
r
d
p
[
i
−
1
]
[
j
]
dp[i][j] = \begin{cases} \ if\ s[i]=p[j]:\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ dp[i-1][j-1]\\ \ if\ p[j]='*'\ \&\ s[i]\ne p[j-1]:\ dp[i][j-2]\\ \ if\ p[j]='*'\ \&\ s[i]= p[j-1]:\ dp[i][j-2]\ or\ dp[i-1][j] \end{cases}
dp[i][j]=⎩⎪⎨⎪⎧ if s[i]=p[j]: dp[i−1][j−1] if p[j]=′∗′ & s[i]=p[j−1]: dp[i][j−2] if p[j]=′∗′ & s[i]=p[j−1]: dp[i][j−2] or dp[i−1][j]
#include <bits/stdc++.h>
using namespace std;
string s, p;
bool dp[50][50];
bool check(int i, int j) {
if (i == 0) return false;
if (p[j-1] == '.') return true;
return s[i-1] == p[j-1];
}
int main() {
while (cin >> s >> p) {
int m = s.size();
int n = p.size();
memset(dp, false, sizeof dp);
dp[0][0] = true;
for (int i = 0; i <= m; i++)
for (int j = 1; j <= n; j++)
if (p[j-1] == '*') {
dp[i][j] |= dp[i][j-2];
if (check(i, j-1))
dp[i][j] |= dp[i-1][j];
} else if (check(i, j)) {
dp[i][j] |= dp[i-1][j-1];
}
if (dp[m][n])
cout << "Yes" << '\n';
else
cout << "No" << '\n';
}
return 0;
}