不重复的子段数量
问题 给你一个由小写字符组成字符串,问由多少连续子串没有重复字符。
解法一
枚举右端点,因为只由26个小写字母组成,所以可能合法的左端点只有26个,依次判断即可。
复杂度O(26n)。
解法二
令
f
(
r
)
f(r)
f(r)表示r对应的最小且合法的左端点。
我们发现有性质
f
(
r
)
<
=
f
(
r
+
1
)
f(r) <= f(r + 1)
f(r)<=f(r+1)。
因此,左右端点各扫描一次,复杂度O(n)。
核心代码
int solve() {
int ans = 0;
for (int l = 1, r = 1; r <= n; r++) {
cnt[str[r]]++;
while (cnt[str[r]] > 1) cnt[str[l++]]--;
ans += r - l + 1;
}
return ans;
}
方格染色
问题
给定n个格子排成一条直线,对每个格子染黑色或者白色,要求黑色的格子不能相邻,问染色方案数。
解法一
考虑使用动态规划,设dp[i][0]表示已经合法的染了前i个格子,且第i个格子的颜色是白色,dp[i][1]表示已经合法的染了前i个格子,且第i个格子的颜色是黑色。
当第i+1个格子要染白色时,第i个格子染黑白都可以。dp[i + 1][0]=dp[i][0]+dp[i][1]。
当第i+1个格子要染黑色时,第i个格子只能染白色。dp[i+1][1]=dp[i][0]。
复杂度O(n)。
解法二
设f(n)表示染n个格子,且每个格子都没有额外限制的方案数。
若第1个格子染白色,则后面n-1个格子没有额外限制,有f(n-1)种方案。
若第1个格子染黑色,则第二个格子一定染白色,后面n-2个格子没有额外限制,有f(n-2)种方案。
所以f(n)=f(n-1)+f(n-2)。
复杂度O(n)
解法三
考虑构造如下矩阵。
[
d
p
[
i
+
1
]
[
0
]
d
p
[
i
+
1
]
[
1
]
]
=
[
1
1
1
0
]
[
d
p
[
i
]
[
0
]
d
p
[
i
]
[
1
]
]
\begin{bmatrix} dp[i+1][0] \\ dp[i+1][1] \end{bmatrix}=\begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}\begin{bmatrix} dp[i][0] \\ dp[i][1] \end{bmatrix}
[dp[i+1][0]dp[i+1][1]]=[1110][dp[i][0]dp[i][1]]
或
[
f
(
i
)
f
(
i
−
1
)
]
=
[
1
1
1
0
]
[
f
(
i
−
1
)
f
(
i
−
2
)
]
\begin{bmatrix} f(i) \\ f(i-1) \end{bmatrix}=\begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}\begin{bmatrix} f(i-1) \\ f(i-2) \end{bmatrix}
[f(i)f(i−1)]=[1110][f(i−1)f(i−2)]
问题就转化成了系数矩阵的n次幂。快速幂处理。
复杂度
O
(
l
o
g
2
n
)
O(log_2n)
O(log2n)。
共线
问题
给定n个二维平面上的点,问是否存在一条直线至少穿过n/2个点。
解法一
枚举两个点,算斜率和截距,最后判断是否有这条直线。
复杂度
O
(
n
2
)
O(n^2)
O(n2)。
解法二
假设存在这样一条合法的直线,那么我们随机选一个点,且在这条直线上的概率是1/2。那么我们随机选择两个点,在这条直线上的概率是1/4,不在的概率是3/4。每次验证在这条直线上的点的个数,如果合题意,显然存在。如果不是那条直线,那么误判的概率是3/4。假设我们做T次这样的判定,都不存在合法的直线,我们断言不存在这样的直线的误判概率是
(
3
/
4
)
T
(3/4)^T
(3/4)T。
当T取30时,误判的概率已经小于万分之一。当然,选择合适的T即可。
复杂度
O
(
T
n
)
O(Tn)
O(Tn)。
k-element
问题
给你无序的n个数,求第k小元素。这是一个经典问题,已经被c++收录为nth_element函数。
解法一
将原数组排序,直接可得第k小元素。
复杂度取决于排序算法。
解法二
我们随机的从序列中选一个数x,把其他的数分为两类,有a个小于x的,有b个大于x的。
如果a+1==k,则x就是答案。
如果a>=k,则对小于x部分元素求第k小元素。
如果a<k,则对大于x部分求第k-a-1小元素。
因为x是随机选的,每次期望舍弃序列的一半,复杂度=O(n+n/2+n/4+…)=O(n)
3和5和好数
定义 一个数能被3整除或者被5整除则称为好数。比如3,5,6,9,10,12,15都是好数。
问题一:
[
1
,
n
]
[1,n]
[1,n]中有多少好数。
f
(
n
)
=
n
/
3
+
n
/
5
−
n
/
15
f(n) = n/3+n/5-n/15
f(n)=n/3+n/5−n/15
容斥原理,三的倍数有n/3个,5的倍数有n/5个,但是十五的倍数被算了两次。
复杂度
O
(
1
)
O(1)
O(1)。
问题二: 问第n个好数是多少。
解法一:
直接枚举,如果是好数就计数。我们发现答案不超过5n。
复杂度
O
(
n
)
O(n)
O(n)。
解法二:
我们能够发现f(n)是个单调不降函数,因此可以二分。
假设第n个好数在区间[l,r]里,答案肯定在[1,5n]这个区间里。取mid=(l+r)/2,如果f(mid)<n,则答案在[mid+1,r]里,否则答案在[l,mid]里。
每次区间缩小一半,经过
l
o
g
2
n
log_2n
log2n次区间大小缩小为1,即为答案。
复杂度
O
(
l
o
g
2
n
)
O(log_2n)
O(log2n)。
过河
问题 有n个人需要过河,第i个人的体重是wi,保证体重按照递增的顺序给出。一艘船最多能坐两个人,最大承重W,问最小需要几艘船。
解法:
考虑贪心的解决这个问题,两个人的体重和尽可能的接近W。
复杂度O(n)
核心代码
int ans = 0;
for (int l = 1, r = n; l <= r;) {
if (w[l] + w[r] > W) {
ans++; r--;
}else {
ans++; l++; r--;
}
}
k次方和
问题 求
∑
i
=
1
n
i
k
%
2021325
\sum_{i=1}^ni^k\%2021325
∑i=1nik%2021325,。
解法一
直接暴力计算,复杂度O(nk)。
解法二
考虑如何快速计算
i
k
i^k
ik,我们分别计算
i
1
,
i
2
,
i
4
,
i
8
,
i
16
.
.
.
i^1,i^2,i^4,i^8,i^{16}...
i1,i2,i4,i8,i16...后一项是前一项的平方,而k一定能写成不超过logk个数的加和,如13=1+4+8,
i
13
=
i
∗
i
4
∗
i
8
i^{13}=i*i^4*i^8
i13=i∗i4∗i8。
复杂度
O
(
n
l
o
g
2
k
)
O(nlog_2k)
O(nlog2k)。
核心代码
int qpow(int x, int n, int mod) {
int res = 1;
while (n > 0) {
if (n % 2) res = 1LL * res * x % mod;
x = 1LL * x * x % mod;
n /= 2;
}
return res;
}
解法三
我们发现合数能够拆成两个更小的已经被计算过的数,只要求质数的k次幂即可。质数的密度是
n
/
l
o
g
2
n
n/log_2n
n/log2n的。
配合线性筛,复杂度O(n)。
int solve() {
int ans = 1;
memset(is_pri, true, sizeof(is_pri));
for (int i = 2; i < N; i++) {
if (is_pri[i]) {
f[i] = qpow(i, k);
pri[tot++] = i;
}
for (int j = 0; 1LL * i * pri[j] < N; j++) {
f[i * pri[j]] = 1LL * f[i] * f[pri[j]] % MOD;
is_pri[i * pri[j]] = false;
if (i % pri[j] == 0) break;
}
ans = (ans + f[i]) % MOD;
}
return ans;
}