Parade
给
n
n
n个底边与
x
x
x轴重合的矩形,每个矩形用一条边表示
<
l
i
,
r
i
,
h
i
>
<l_i, r_i,h_i>
<li,ri,hi>,求出最后图形的轮廓。
1
≤
n
≤
1
e
5
1 \leq n \leq 1e^5
1≤n≤1e5
题解
观察到只有端点才会成为轮廓点。对于每个端点 x x x,暴力枚举覆盖该端点的最大高度,复杂度 O ( n 2 ) O(n^2) O(n2)
对于一条线段 [ l , r ] [l, r] [l,r],覆盖点 x x x当且仅当 l ≤ x < r l \leq x < r l≤x<r
优化的点在于:给一个端点,怎么求这个最大值?
- 扫描线的思想 + 线段树:修改一个区间,维护当前区间的最大值
- 将线段按照左端点排序,用优先队列维护最大值,求覆盖当前点的最大高度.
买卖股票
给出一支股票的
n
n
n天走势,问最多交易
k
k
k次所获得的最大利润,注意只有交易完成后才能再次购买。
2
≤
2
∗
k
≤
n
≤
1000
2 \leq 2 *k \leq n \leq 1000
2≤2∗k≤n≤1000
题解
令
d
p
[
i
]
[
2
∗
j
−
1
]
dp[i][2 *j - 1]
dp[i][2∗j−1]表示前
i
i
i天第
j
j
j次买入股票所能获得最大的利润,
d
p
[
i
]
[
2
∗
j
]
dp[i][2 *j]
dp[i][2∗j]表示前
i
i
i天第
j
j
j次卖出股票所能获得最大的利润,有:
d
p
[
i
]
[
2
∗
j
−
1
]
=
m
a
x
(
d
p
[
i
−
1
]
[
2
∗
j
−
1
]
,
d
p
[
i
−
1
]
[
2
∗
(
j
−
1
)
]
−
p
r
i
c
e
s
[
i
]
)
d
p
[
i
]
[
2
∗
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
2
∗
k
]
,
d
p
[
i
−
1
]
[
2
∗
j
−
1
]
+
p
r
i
c
e
s
[
i
]
)
dp[i][2 * j - 1] = max(dp[i - 1][2 * j - 1], dp[i - 1][2*(j-1)] - prices[i]) \\ dp[i][2 * j] = max(dp[i - 1][2 * k], dp[i - 1][2 * j - 1] + prices[i])
dp[i][2∗j−1]=max(dp[i−1][2∗j−1],dp[i−1][2∗(j−1)]−prices[i])dp[i][2∗j]=max(dp[i−1][2∗k],dp[i−1][2∗j−1]+prices[i])
初始化:
d
p
[
0
]
[
1
,
3
,
4
,
⋯
,
(
2
∗
k
−
1
)
]
=
−
p
r
i
c
e
s
[
0
]
dp[0][1,3,4,\cdots,(2 * k - 1)] = -prices[0]
dp[0][1,3,4,⋯,(2∗k−1)]=−prices[0]。
答案:
d
p
[
n
−
1
]
[
2
∗
k
]
dp[n - 1][2 * k]
dp[n−1][2∗k]
戳气球
给
n
n
n个气球,第
i
i
i个气球的价值为
v
i
v_i
vi(
i
i
i从1开始),戳破一个气球得到硬币为:
v
p
∗
v
i
∗
v
q
v_{p} * v_i * v_{q}
vp∗vi∗vq,即与气球
i
i
i左相邻(如果存在)及右相邻(如果存在)的价值乘积。问戳完所有气球所能获得的最多硬币。
1
≤
n
≤
200
1 \leq n \leq 200
1≤n≤200
题解
令
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示戳破开区间
(
i
,
j
)
(i, j)
(i,j)中的所有气球获得最大利润,答案为
d
p
[
0
]
[
n
+
1
]
dp[0][n + 1]
dp[0][n+1],考虑枚举该区间内最后被戳破的气球,有:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
]
[
j
]
,
m
a
x
p
o
s
=
l
+
1
r
−
1
(
v
[
i
]
∗
v
[
p
o
s
]
∗
v
[
r
]
+
d
p
[
i
]
[
p
o
s
]
+
d
p
[
p
o
s
]
[
r
]
)
)
dp[i][j] = max(dp[i][j], max_{pos = l + 1}^{r - 1}(v[i] * v[pos] * v[r] + dp[i][pos] + dp[pos][r]))
dp[i][j]=max(dp[i][j],maxpos=l+1r−1(v[i]∗v[pos]∗v[r]+dp[i][pos]+dp[pos][r]))
为什么不考虑枚举区间内第一个被戳破的气球?因为戳破一个气球后,原本不相邻的两个气球会变得相邻,从而无法处理。
地下城游戏
给一个二维数组
A
n
∗
m
A_{n*m}
An∗m,假如你的生命值为正整数
H
H
H,从
(
1
,
1
)
(1,1)
(1,1)走到
(
n
,
m
)
(n,m)
(n,m),每次只能向右或者向下移动一格。
A
i
j
≥
0
A_{ij} \geq 0
Aij≥0则你的生命值增加,反之则减少。如果
H
≤
0
H \leq 0
H≤0,则无法移动。问移动到
(
n
,
m
)
(n,m)
(n,m)所要求的最少
H
H
H?
1
≤
n
,
m
≤
1000
1 \leq n,m \leq 1000
1≤n,m≤1000
题解
顺着
d
p
dp
dp整不出来,用二分莽了一波,正解是从右下角向左上角递推:令
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示从
(
i
,
j
)
(i,j)
(i,j)走到
(
n
,
m
)
(n,m)
(n,m)所需要的最少生命值
H
H
H,有
d
p
[
i
]
[
j
]
=
m
a
x
(
m
i
n
(
d
p
[
i
+
1
]
[
j
]
,
d
p
[
i
]
[
j
+
1
]
)
−
A
i
j
,
1
)
dp[i][j] = max(min(dp[i + 1][j], dp[i][j + 1]) - A_{ij}, 1)
dp[i][j]=max(min(dp[i+1][j],dp[i][j+1])−Aij,1)
和戳气球那题一样,很有意思!
打砖块
矩阵 A n ∗ m A_{n * m} An∗m只包含0,1,如果某个含1的位置 P P P,沿着含1的位置走到了(上下左右走都皆可)第一行含1的位置,则 P P P是稳定的,否则 P P P是不稳定的。现给 q q q次操作,每次消除一个位置上的1(该位置可能没有1),问该次操作导致的不稳定位置有多少个?注意每次操作以后,不稳定的位置上的1都会变成0,只有含1的位置才能判断它是不是稳定的。
1 ≤ n , m ≤ 1000 1 \leq n, m \leq 1000 1≤n,m≤1000, 1 ≤ q < n ∗ m 1 \leq q < n * m 1≤q<n∗m
题解
正序考虑的话没想到其它解法,只有暴力。如果逆序的话,考虑直接执行 q q q次操作后的状态,此时含1的位置组成了一个个连通块,根据这些联通块怎么推出每次击打的贡献呢?注意到一个连通块的产生是因为某次击打使之在原来的基础上多了一个块出来,故相反之,我们可以考虑添加一个1,从而合并两个连通块。综上:如果一个联通块因为在某个位置添加一个1而与第一行相连,故该联通块的大小就是操作该位置的贡献之一。从后向前考虑,对于第 i i i 次操作,先将它与 能和第一行相连的连通块合并,然后再统计贡献。注意:不能边合并边统计贡献,因为枚举相邻位置的顺序会产生影响
还有更妙的方法统计贡献,考虑每添加一个点,包含超级源点的连通块的大小变化,多出来的就是因为操作这个位置而导致的非稳定点的个数 !!!
和戳气球那题一样,很有意思!
按要求补齐数组
给一个已经排序好了的数组 n u m s nums nums:问最少添加几个数到 n u m s nums nums中,使得 [ 1 , n ] [1,n] [1,n]区间内的任何数能由某几个 n u m s nums nums中的数之和表示。
1 ≤ n ≤ 2 32 1 \leq n \leq 2^{32} 1≤n≤232
题解
注意到假设
n
u
m
s
nums
nums中的数能表示
[
1
,
x
−
1
]
[1, x-1]
[1,x−1],那么加上
x
x
x以后,则能表示
[
1
,
2
∗
x
−
1
]
[1, 2*x - 1]
[1,2∗x−1]。初始令
x
=
1
x = 1
x=1,然后根据
n
u
m
s
nums
nums中的数不断更新
x
x
x的值,直到
x
>
n
x > n
x>n。
难点在于维护
x
x
x!
最长严格递增子序列的长度和个数
给一个长度为 n n n的数组,求最长的严格递增子序列的长度及其个数?
1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1≤n≤105
题解
先来看怎么求最长递增子序列的长度,令
d
p
[
i
]
dp[i]
dp[i]表示长度为
i
i
i的严格递增子序列的末尾元素最小值(有多个长度为
i
i
i的严格递增子序列)。可以证明:
d
p
[
]
dp[]
dp[]是递增的。
证明如下:假设
j
<
i
j < i
j<i,有
d
p
[
j
]
≥
d
p
[
i
]
dp[j] \geq dp[i]
dp[j]≥dp[i],从长度为
i
i
i且末尾元素为
d
p
[
i
]
dp[i]
dp[i]的严格递增子序列中删除末尾的
i
−
j
i - j
i−j个元素,剩下的前
j
j
j个元素依然是严格递增的,假设该序列中第
j
j
j个元素为
x
x
x,根据定义有:
d
p
[
j
]
<
=
x
<
d
p
[
i
]
dp[j] <= x < dp[i]
dp[j]<=x<dp[i],与假设矛盾,故
d
p
[
]
dp[]
dp[]严格递增。
故只需要从头到尾依次遍历数组的同时,维护
d
p
[
]
dp[]
dp[]和当前最长的严格递增子序列的长度
l
e
n
len
len,好题!复杂度
O
(
n
l
g
n
)
O(nlgn)
O(nlgn)。
求长度依赖这样一个贪心的思想:每次加入严格递增子序列的元素应该尽可能小!
在上述求长度的过程中,可以多维护一些信息,比如长度为
l
e
n
len
len的严格递增子序列的末尾元素,说通俗点,也就是把这些元素放进对应
l
e
n
len
len的桶中。用二维数组
d
[
i
]
[
]
d[i][]
d[i][]存储长度为
i
i
i的严格递增子序列的末尾元素,令
c
n
t
[
i
]
[
j
]
cnt[i][j]
cnt[i][j]表示长度为
i
i
i,末尾元素为
A
[
j
]
A[j]
A[j]的严格递增子序列的个数,有:
c
n
t
[
i
]
[
j
]
=
∑
k
=
0
c
n
t
[
i
−
1
]
[
k
]
&
(
d
[
i
−
1
]
[
k
]
<
A
[
j
]
)
cnt[i][j] = \sum_{k = 0}cnt[i - 1][k] \& (d[i - 1][k] < A[j])
cnt[i][j]=k=0∑cnt[i−1][k]&(d[i−1][k]<A[j])
注意到
d
[
i
]
d[i]
d[i]中的元素是单调递增的,故可以用二分查找下标
k
k
k,同时维护前缀和,将求和的复杂度降至
O
(
l
g
n
)
O(lgn)
O(lgn)。总的时间复杂度
O
(
n
l
g
n
)
O(nlgn)
O(nlgn),空间复杂度
O
(
n
)
O(n)
O(n),因为每个元素只会在一个桶里面。
最大整除子集
给一个长度为 n n n的数组 A A A,求长度最长的一个子集,使得该子集内任意两个元素 a , b a,b a,b满足: b % a = 0 b \% a = 0 b%a=0或者 a % b = 0 a \% b = 0 a%b=0。
1 ≤ n ≤ 1 0 3 1 \leq n \leq 10^3 1≤n≤103
题解
注意是子集,而不是子序列,说明可以对原数组进行排序。考虑对于一个整除子集,怎么往里面添加一个数
x
x
x,使得它的长度加1?假设该子集中元素最大值为
m
a
x
max
max,最小值为
m
i
n
min
min,如果
x
%
m
a
x
=
0
x \% max = 0
x%max=0或者
m
i
n
%
x
=
0
min \% x = 0
min%x=0,那么将
x
x
x加入,该集合依然是整除子集。将原数组按升序排序,令
d
p
[
i
]
dp[i]
dp[i]表示以
A
[
i
]
A[i]
A[i]作为最大值的最大整除子集的大小(长度),有:
d
p
[
i
]
=
m
a
x
(
d
p
[
j
]
)
+
1
,
(
j
∈
{
0
,
1
,
2
,
⋯
,
i
−
1
}
,
且
A
[
i
]
%
A
[
j
]
=
0
)
dp[i] = max(dp[j]) + 1,(j \in \{0, 1, 2, \cdots , i - 1\} ,且A[i] \% A[j] = 0)
dp[i]=max(dp[j])+1,(j∈{0,1,2,⋯,i−1},且A[i]%A[j]=0)
至于求出这个最大子集,使用一个
p
r
e
v
[
]
prev[]
prev[]数组记录一下状态转移就行了。
可怜的小猪
有 m m m桶水, n n n轮测试,问至少需要几只诸参与测试才能判断出哪桶水有毒?(只会有一桶水有毒),如果一只猪在当前轮喝到有毒的水,那么在这轮结束后会死亡。
1 ≤ m ≤ 1000 1 \leq m \leq 1000 1≤m≤1000, 1 ≤ n ≤ 100 1 \leq n \leq 100 1≤n≤100
题解
令
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示
j
j
j轮测试,
i
i
i只猪能判断出哪桶水有毒的情况下的最多桶水,有:
d
p
[
i
]
[
j
]
=
∑
k
=
0
i
C
i
k
d
p
[
i
−
k
]
[
j
−
1
]
=
(
j
+
1
)
i
dp[i][j] = \sum_{k = 0}^{i}C_i^{k}dp[i - k][j - 1] = (j + 1)^i
dp[i][j]=k=0∑iCikdp[i−k][j−1]=(j+1)i含义:进行一轮测试后,假如有
k
k
k只猪死去,那么剩下
i
−
k
i - k
i−k只猪和
j
−
1
j - 1
j−1轮测试,为什么还要乘一个组合数喃?同一只猪不会在一轮测试中喝同一桶水多次,故这些猪可以看作是不同的。
需要的最少猪猪数也就是满足
d
p
[
i
]
[
n
]
≥
m
dp[i][n] \geq m
dp[i][n]≥m的
i
i
i。
二项式定理可以证明右边相等部分。实际上这道题也可以构造出答案,也就是设计出猪猪的喝水方案,将这些桶按照 n + 1 n + 1 n+1进制一一编号,对于桶 i i i,假设它的 n + 1 n + 1 n+1进制表示为 x 0 x 1 x . . . x t , x i ∈ { 0 , 1 , 2 , ⋯ , n } x_0x_1x_{...}x_t,x_i \in \{0, 1, 2, \cdots , n\} x0x1x...xt,xi∈{0,1,2,⋯,n},编号为0的猪在 x 0 x_0 x0轮喝这桶水,如果 x 0 = 0 x_0 = 0 x0=0,则 n n n轮测试中都不喝这桶水。然后根据最终测试结果中,死的猪的编号以及它死在哪一轮,就可以推断出有毒的那桶水的编号
超级洗衣机
n n n台洗衣机,每轮挑选 m m m台,并从每台机器中拿出一件衣服放到与之相邻的机器中,问至少操作几轮使得 n n n台机器里面的衣服数都相同?
1 ≤ n ≤ 1 e 5 1 \leq n \leq 1e5 1≤n≤1e5
题解
记前面 i i i台机器为 A A A组,后面 n − i n - i n−i台机器为 B B B组,为了达到最终的平衡,存在两种情况:
- 第 i i i台机器衣服太多,需要拿出衣服放到左右两边的机器中
- A A A组的衣服经过第 i i i台机器流向 B B B组,或者 B B B组的衣服经过第 i i i台机器流向 A A A组
两种情况取最大值就行了,分析经过每台机器的流量是此题的切入点,好题!
奇怪的打印机
有 n n n个点组成的序列 A A A,每次给一个区间染色,已知最终这 n n n个点的颜色,问最少的染色次数。
1 ≤ n ≤ 100 1 \leq n \leq 100 1≤n≤100
题解
对于一个区间,如果两端的颜色相等,那么当打印右端点的时候顺便染色左端点,或者相反。如果两端的颜色不相等,则左端点需要染色一次,右端点需要染色一次,因此这个区间可以分成两部分计算,为什么不是三部分甚至更多?不需要,可以仔细想想。令
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示区间
[
i
,
j
]
[i,j]
[i,j]需要最少的染色次数,有
d
p
[
i
]
[
j
]
=
{
m
i
n
(
d
p
[
i
]
[
j
−
1
]
,
d
p
[
i
+
1
,
j
]
)
A
[
i
]
=
A
[
j
]
m
i
n
(
d
p
[
i
]
[
j
]
,
d
p
[
i
]
[
k
]
+
d
p
[
k
+
1
]
[
j
]
)
A
[
i
]
≠
A
[
j
]
,
k
∈
{
i
,
i
+
1
,
⋯
,
j
−
1
}
dp[i][j] = \begin{cases} min(dp[i][j - 1],dp[i + 1, j]) & A[i] = A[j] \\ min(dp[i][j], dp[i][k] + dp[k + 1][j]) & A[i] \neq A[j],k \in \{i, i + 1,\cdots ,j -1\} \end{cases}
dp[i][j]={min(dp[i][j−1],dp[i+1,j])min(dp[i][j],dp[i][k]+dp[k+1][j])A[i]=A[j]A[i]=A[j],k∈{i,i+1,⋯,j−1}
复杂度
O
(
n
3
)
O(n^3)
O(n3)
不同的回文子序列个数
给一个字符串 S S S,问有多少个不同的回文子序列?
1 ≤ S . s i z e ≤ 1000 1 \leq S.size \leq 1000 1≤S.size≤1000
题解
令 d p [ i ] [ j ] dp[i][j] dp[i][j]表示区间 [ i , j ] [i,j] [i,j]的不同回文子序列个数,讨论两个端点 S [ i ] S[i] S[i]和 S [ j ] S[j] S[j],有
- S [ i ] = = S [ j ] S[i] == S[j] S[i]==S[j],记区间 [ i + 1 , j − 1 ] [i + 1, j - 1] [i+1,j−1]的不同回文子序列组成的集合为 P P P,显然 P P P的元素加上这两端也是一个回文子序列,问题来了,可能会重复,比如 P = { b , a b a } P = \{b, aba \} P={b,aba}, S [ i ] = a S[i] = a S[i]=a,显然 S [ i ] + b + S [ j ] S[i] + b + S[j] S[i]+b+S[j] 和 a b a aba aba 重复了,怎么去重?先考虑哪些子序列会发生重复,正如上面那个例子所示,会重复的子序列就是: P P P中的某些元素加上这两端组成的子序列已经是 P P P中的元素了,这部分的元素个数就是 d p [ l + 1 ] [ r − 1 ] dp[l + 1][r - 1] dp[l+1][r−1], l l l是 [ i + 1 , j − 1 ] [i + 1, j - 1] [i+1,j−1]内第一个等于 S [ i ] S[i] S[i]元素的位置, r r r是 [ i + 1 , j − 1 ] [i + 1, j - 1] [i+1,j−1]内最后一个等于 S [ i ] S[i] S[i]元素的位置。注意两端也可以组成两个不同的回文子序列。
- S [ i ] ≠ S [ j ] S[i] \neq S[j] S[i]=S[j],记区间 [ i + 1 , j ] [i + 1, j] [i+1,j]的不同回文子序列组成的集合为 P 1 P_1 P1,区间 [ i , j − 1 ] [i , j - 1] [i,j−1]的不同回文子序列组成的集合为 P 2 P_2 P2,记区间 [ i + 1 , j − 1 ] [i + 1, j - 1] [i+1,j−1]的不同回文子序列组成的集合为 P 3 P_3 P3,有 P 1 ⋂ P 2 = P 3 P_1 \bigcap P_2 = P_3 P1⋂P2=P3。
综上所述,有:
d
p
[
i
]
[
j
]
=
{
d
p
[
i
+
1
]
[
j
]
+
d
p
[
i
]
[
j
−
1
]
−
d
p
[
i
+
1
]
[
j
−
1
]
S
[
i
]
≠
S
[
j
]
2
∗
d
p
[
i
+
1
]
[
j
−
1
]
+
2
−
{
1
l
=
=
r
d
p
[
l
+
1
]
[
r
−
1
]
+
2
l
<
r
S
[
i
]
=
S
[
j
]
dp[i][j] = \begin{cases} dp[i + 1][j] + dp[i][j - 1] - dp[i + 1][j - 1] & S[i] \neq S[j] \\ 2 * dp[i + 1][j - 1] + 2 - \begin{cases} 1 & l == r \\ dp[l + 1][r - 1] + 2 & l < r \end{cases} & S[i] = S[j] \end{cases}
dp[i][j]=⎩⎪⎨⎪⎧dp[i+1][j]+dp[i][j−1]−dp[i+1][j−1]2∗dp[i+1][j−1]+2−{1dp[l+1][r−1]+2l==rl<rS[i]=S[j]S[i]=S[j]
摘樱桃
给一个正方形的地,先从 ( 0 , 0 ) (0, 0) (0,0)向下或者向右走到 ( n − 1 , n − 1 ) (n - 1, n - 1) (n−1,n−1),再向上或者向左回到 ( 0 , 0 ) (0,0) (0,0),问这个过程中最多能摘到的樱桃数?每个位置有一个数字(小于0则表示此位置无法经过)代表可摘的樱桃数。
1 ≤ n ≤ 100 1 \leq n \leq 100 1≤n≤100
题解
很容易想到一个错误做法,就是贪心,遗憾的是:两个次优解可以组成一个全局最优解。正确的做法是把串行变成并行,即假设有两个人同时出发,从 ( 0 , 0 ) (0,0) (0,0)走到 ( n − 1 , n − 1 ) (n - 1, n - 1) (n−1,n−1),求他们总共能摘到的最多樱桃数。假设一个人走了 K K K步,其中向下走了 r o w row row步,则此时他所在的位置为 ( r o w , K − r o w ) (row, K - row) (row,K−row)。令 d p [ K ] [ r o w 1 ] [ r o w 2 ] dp[K][row_1][row_2] dp[K][row1][row2]表示两个人分别走了 K K K步,其中1号走到了 ( r o w 1 , K − r o w 1 ) (row_1, K - row_1) (row1,K−row1),2号走到了 ( r o w 2 , K − r o w 2 ) (row_2, K - row_2) (row2,K−row2)处收获的最多樱桃数,然后枚举下一步的走法,即1号向下,2号向下、1号向右,2号向右,等等,对应的更新 d p [ K + 1 ] [ ] [ ] dp[K + 1][][] dp[K+1][][]就行了。注意同一个位置只能采摘一次,可以思考一下为啥可以这样计算,为什么不设 d p [ m ] [ r o w 1 ] [ n ] [ r o w 2 ] dp[m][row_1][n][row_2] dp[m][row1][n][row2],一个人走了 m m m步,另一个人走了 n n n步的最多樱桃数?
最大连续子段和问题的变形
- 给一个长度为 n n n的数组,只翻转一次任意一个子区间,问最大连续子段和?翻转操作: [ 1 , 2 , 3 ] → [ 3 , 2 , 1 ] [1,2,3] \rightarrow [3, 2,1] [1,2,3]→[3,2,1]
- 给一个长度为 n n n的数组,只取反一次任意一个子区间,问最大连续子段和?取反操作: [ 1 , − 2 , 3 ] → [ − 1 , 2 , − 3 ] [1,-2,3] \rightarrow [-1, 2, -3] [1,−2,3]→[−1,2,−3]
1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1≤n≤105
问题1的解
其实不用考虑具体翻转哪个区间,分别求出 [ 1 , i ] [1, i] [1,i]的最大连续子段和 s u m a sum_a suma,和 [ i + 1 , n ] [i + 1, n] [i+1,n]的最大连续子段和 s u m b sum_b sumb,记 a n s i = s u m a + s u m b ans_i = sum_a + sum_b ansi=suma+sumb,则最终答案就是取最大值 m a x ( a n s 1 , a n s 2 , ⋯ , a n s n ) max(ans_1, ans_2, \cdots,ans_n) max(ans1,ans2,⋯,ansn)。问什么可以这么做?翻转操作可以将不连续的两段变成连续的两段。
问题2的解
特殊的二进制序列
对于一个只含有0,1的字符串 S S S, S S S是特殊的当且仅当满足:
- 0和1的个数相同
- 对于 S S S的任意一个前缀 s s s, s s s中1的个数大于等于0的个数
操作:选择 S S S的两个 相邻 特殊子串 交换位置,比如 10 / 1100 → 1100 / 10 10/1100 \rightarrow 1100/10 10/1100→1100/10。问任意次操作下,字典序最大的 S S S
题解
很明显的一点是特殊串第一个字符是1,最后一个字符是0。比较难观察的是: S S S是由几个不相交的特殊子串连接而成的(尽可能的拆分 S S S,即每个特殊子串不可再拆分)。假如我们已经知道了每个特殊子串最优的排列,那么将这些子串按照从大到小排序(任意次的两两交换可以使得它们从大到小),然后连接而成的 S S S字典序最大。现在问题转化成了任意次操作下每个特殊子串的最大字典序排列,刚好是原问题的子问题!
如果将1看作是左括号,0看作右括号,那么上述的两个限制恰好保证 S S S是一个合法的括号序列。显然一个合法的括号序列可以拆分成几个不相交 子合法括号序列,如()(()()) = () + (()())
破解保险箱
一个密码是由 n n n位 k k k进制下的数字组成,求最短的字符串,使得 可能的密码 都是它的子串。
题解
将密码的前 n − 1 n - 1 n−1位( a 1 a 2 ⋯ a n − 1 a_1a_2 \cdots a_{n - 1} a1a2⋯an−1)看成一个顶点,连 k k k条边出去,第 i i i条边连向的顶点是 a 2 a 3 ⋯ a n − 1 i a_2a_3 \cdots a_{n - 1} i a2a3⋯an−1i。显然,每个顶点的入度等于出度,且顶点和一条出边组成了一个密码。遍历完所有的边再加上开始遍历的点组成的字符串包含了所有可能的密码,而且每个点的度数都是偶数个,说明存在一条欧拉闭迹,即每条边只经过一次,故该字符串是最短的。
棋盘
给一个 n ∗ n n * n n∗n大小且只含0,1的棋盘,每次可以任意交换一行或者一列,问最少交换几次使得任意一个位置与其相邻(有一条共享的边)的位置不同。
1 ≤ n ≤ 1000 1 \leq n \leq 1000 1≤n≤1000
题解
找规律。注意到满足任意一个位置的数与其相邻位置的数都不同,这样的棋盘只含有两种不同的行(01010… 和 10101…)。任意两行不管怎么交换列,都不会影响它们的关系,即不同行经过任意列交换还是不同行,相同行任意列交换还是相同行。明白这个就会知道,初始棋盘应该也只含有两种不同的行(否则无解),并且这两行互补(否则怎么交换,都有相邻的上下位置相同),举个🌰,101 和 010 就是互补的。最后计算调整列需要的最少次数,调整行需要多少次就行了!
得分最高的最小轮调
给一个数组 A A A,下标 k k k 的轮调就是将数组前后部分换个位置,即 a 0 , a 1 , a 2 , ⋯ , a n − 1 → a k , a k + 1 , a k + 2 , ⋯ , a n − 1 , / a 1 , a 2 , ⋯ , a k − 1 a_0, a_1, a_2, \cdots,a_{n-1} \rightarrow a_k,a_{k + 1}, a_{k + 2}, \cdots, a_{n - 1}, / a_1, a_2, \cdots, a_{k - 1} a0,a1,a2,⋯,an−1→ak,ak+1,ak+2,⋯,an−1,/a1,a2,⋯,ak−1,轮调后,如果 A [ i ] ≤ i A[i] \leq i A[i]≤i, i ∈ { 0 , 1 , 2 , ⋯ , n − 1 } i \in \{0, 1, 2, \cdots, n - 1\} i∈{0,1,2,⋯,n−1},则记一分,问得分数最多的 k k k,如果有多个位置,输出最小的那个。
1 ≤ s i z e ( A ) ≤ 1 0 5 1 \leq size(A) \leq 10^5 1≤size(A)≤105, 0 ≤ A [ i ] < s i z e ( A ) 0 \leq A[i] < size(A) 0≤A[i]<size(A)
题解
可以预处理出每个位置在哪些 k k k (一个连续的区间)中会得分。分两部分计算,前一部分调到后一部分的贡献,和后调前的贡献,最后相加求最大值就行了。
还有种思路是动态规划,考虑从小到大枚举 k k k(下标),注意到每一次轮调在前一次轮调的基础上,每个值对应的下标会减小1,故如果已经知道了 k − 1 k - 1 k−1的轮调得分,那么 k k k 的轮调得分就是 k − 1 k - 1 k−1 的轮调得分 减去 k − 1 k - 1 k−1 次轮调得到的 A A A中 A [ i ] = = i A[i] == i A[i]==i 的数量 再加上 k − 1 k - 1 k−1 轮调 A A A 的第一个元素 在 k k k 轮调处于最后一个位置而因此贡献的得分1。问题转化为求 k k k 轮调得到的 A A A中有几个 A [ i ] = = i A[i] == i A[i]==i,记为 c n t [ k ] cnt[k] cnt[k]。换个思路,初始状态中 A [ i ] A[i] A[i]要经过几轮才能和 i i i相等呢?如果 A [ i ] ≥ i A[i] \geq i A[i]≥i,显然需要经过 A [ i ] − i A[i] - i A[i]−i次轮调;否则需要 i + n − A [ i ] i + n - A[i] i+n−A[i]次轮调。综上,我们可以很简单的预处理出 c n t [ k ] cnt[k] cnt[k]。
复杂度 O ( n ) O(n) O(n)
数组的均值分割
给一个长为 n n n的数组 a r r arr arr,问能否分割成平均值相等的非空两部分?
2 ≤ n ≤ 30 2 \leq n \leq 30 2≤n≤30
题解
暴力计算的范围太大,看能不能将范围减少一半。记这两部分为
A
A
A,
B
B
B,其中
A
A
A的长度为
K
K
K,按照题意有:
s
u
m
(
A
)
K
=
s
u
m
(
B
)
n
−
K
\frac{sum(A)}{K} = \frac{sum(B)}{n - K}
Ksum(A)=n−Ksum(B)
整理得:
s
u
m
(
A
)
K
=
s
u
m
(
A
)
+
s
u
m
(
B
)
n
=
s
u
m
(
a
r
r
)
n
\frac{sum(A)}{K} = \frac{sum(A) + sum(B)}{n} = \frac{sum(arr)}{n}
Ksum(A)=nsum(A)+sum(B)=nsum(arr)
故数组
a
r
r
arr
arr中的每个元素减去一个平均值得到一个新的数组
n
e
w
_
a
r
r
new\_arr
new_arr,问题就转化为了:在新的数组中找到一个子序列使得它的和为0。既然是求和,那就好办了,分成两部分,先暴力求出
n
2
\frac{n}{2}
2n范围内的所有子序列的和,复杂度为
O
(
2
n
/
2
)
O(2^{n / 2})
O(2n/2),然后用相同的方法求剩下的
n
2
\frac{n}{2}
2n的所有子序列的和,最后组装一下就可以了。注意到平均数是可能是小数,故可以将
a
r
r
arr
arr中的每个元素先乘以
n
n
n,再去构造新的数组
n
e
w
_
a
r
r
new\_arr
new_arr。
折半搜索的典型应用