邻项交换排序是竞赛中一种常用、易掌握的贪心策略,用于解决安排顺序一类问题;但是,这同时也是一个易错的算法,若在比赛不注意细节,很容易使贪心策略错误。
邻项交换排序的思路大致如下
1、从头开始验证相邻是否满足最优子结构,若不是,交换相邻两项 2、重复执行1至每相邻两项均为最优子结构[NOIP2012]提高组D1T2 国王游戏
恰逢 H 国国庆,国王邀请 n n n 位大臣来玩一个有奖游戏。首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、右手上各写一个整数。然后,让这 n n n 位大臣排成一排,国王站在队伍的最前面。排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。
国王不希望某一个大臣获得特别多的奖赏,所以他想请你帮他重新安排一下队伍的顺序,使得获得奖赏最多的大臣,所获奖赏尽可能的少。注意,国王的位置始终在队伍的最前面。
第一行包含一个整数 n n n,表示大臣的人数。 第二行包含两个整数 a a a和 b b b,之间用一个空格隔开,分别表示国王左手和右手上的整数。 接下来 n n n 行,每行包含两个整数 a a a 和 b b b,之间用一个空格隔开,分别表示每个大臣左手和右手上的整数。
一个整数,表示重新排列后的队伍中获奖赏最多的大臣所获得的金币数。
3 1 1 2 3 7 4 4 6
2
【输入输出样例说明】 按 1、2、3 这样排列队伍,获得奖赏最多的大臣所获得金币数为 2; 按 1、3、2 这样排列队伍,获得奖赏最多的大臣所获得金币数为 2; 按 2、1、3 这样排列队伍,获得奖赏最多的大臣所获得金币数为 2; 按 2、3、1 这样排列队伍,获得奖赏最多的大臣所获得金币数为 9; 按 3、1、2 这样排列队伍,获得奖赏最多的大臣所获得金币数为 2; 按 3、2、1 这样排列队伍,获得奖赏最多的大臣所获得金币数为 9。 因此,奖赏最多的大臣最少获得 2 个金币,答案输出 2。
【数据范围】 对于 20%的数据,有 1 ≤ n ≤ 10 , 0 < a , b < 8 1≤ n≤ 10,0 < a,b < 8 1≤n≤10,0<a,b<8; 对于 40%的数据,有 1 ≤ n ≤ 20 , 0 < a , b < 8 1≤ n≤ 20,0 < a,b < 8 1≤n≤20,0<a,b<8; 对于 60%的数据,有 1 ≤ n ≤ 100 1≤ n≤100 1≤n≤100; 对于 60%的数据,保证答案不超过 1 0 9 10^9 109; 对于 100%的数据,有 1 ≤ n ≤ 1000 , 0 < a , b < 10000 1 ≤ n ≤1000 , 0 < a,b < 10000 1≤n≤1000,0<a,b<10000。
首先,不难发现,交换相邻两个数对前后均无影响 设 w [ i ] w[i] w[i]为第 i i i个大臣获得金币数 l [ 0 ] , r [ 0 ] l[0],r[0] l[0],r[0]为国王左右手, l [ 1... n ] , r [ 1... n ] l[1...n],r[1...n] l[1...n],r[1...n]为n个大臣左右手 设 i , j i,j i,j,且 i , j i,j i,j相邻。 i , j i,j i,j前所有大臣(包括国王)左手数字之和为 k k k 这两个中最大值 m a x k = m a x { k r [ i ] , k ∗ l [ i ] r [ j ] } ; maxk = max\{\frac{k}{r[i]} , \frac{k*l[i]}{r[j]}\} ; maxk=max{r[i]k,r[j]k∗l[i]}; 若交换i,j大臣最大值则为 m a x k = m a x { k ∗ l [ j ] r [ i ] , k r [ j ] } ; maxk = max\{\frac{k*l[j]}{r[i]} , \frac{k}{r[j]}\} ; maxk=max{r[i]k∗l[j],r[j]k}; 依照贪心思想,maxk 变小,最终就不会变差,且可能更优,所以i在j前面的条件是 m a x { k r [ i ] , k ∗ l [ i ] r [ j ] } ≤ m a x { k ∗ l [ j ] r [ i ] , k r [ j ] } max\{\frac{k}{r[i]} , \frac{k*l[i]}{r[j]}\} \leq max\{\frac{k*l[j]}{r[i]} , \frac{k}{r[j]}\} max{r[i]k,r[j]k∗l[i]}≤max{r[i]k∗l[j],r[j]k} 因为 k ≥ 1 k \geq 1 k≥1 所以 k ∗ l [ i ] r [ j ] ≥ k r [ j ] \frac{k*l[i]}{r[j]} \geq \frac{k}{r[j]} r[j]k∗l[i]≥r[j]k && k ∗ l [ j ] r [ i ] ≥ k r [ i ] \frac{k*l[j]}{r[i]} \geq \frac{k}{r[i]} r[i]k∗l[j]≥r[i]k 所以只需证明 k ∗ l [ i ] r [ j ] ≤ k ∗ l [ j ] r [ i ] \frac{k*l[i]}{r[j]} \leq \frac{k*l[j]}{r[i]} r[j]k∗l[i]≤r[i]k∗l[j] 化简得 l [ i ] ∗ r [ i ] ≤ l [ j ] ∗ r [ j ] l[i]*r[i] \leq l[j]*r[j] l[i]∗r[i]≤l[j]∗r[j] 若找到邻项不满足条件该条件则交换i,j两项
这个程序时间复杂度为 O ( n 2 ) O(n^2) O(n2),效率的瓶颈在排序,一个冒泡排序的算法很难满足OI中的效率要求,所以我们想到用快排的方法优化算法
这个代码在OJ上只能得60分,满分代码需用高精,然鹅这显然不在我要讲的范围内
将排序方式从冒泡排序改为快速排序并不一定正确,这个问题在下一个例题中具体讨论。
皇后游戏(国王游戏的升级版)
皇后有 n 位大臣,每位大臣的左右手上面分别写上了一个正整数。恰逢国庆节来临,皇后决定为 n 位大臣颁发奖金,其中第 i 位大臣所获得的奖金数目为第i-1 位大臣所获得奖金数目与前 i 位大臣左手上的数的和的较大值再加上第 i 位大臣右手上的数。 形式化地讲:我们设第 i 位大臣左手上的正整数为 ai,右手上的正整数为 bi,则第 i 位大臣获得的奖金数目为 ci可以表达为: c i = { a 1 + b 1 , if i = 1 m a x { c i − 1 , ∑ j = 1 i a j } + b i , if 2 ≤ i ≤ n c_i= \begin{cases} a_1+b_1,&\text{if } i \text{ = 1} \\ max\{c_{i-1},\sum_{j = 1}^{i}{a_j}\} + b_i,&\text{if } 2 \leq i \leq n \end{cases} ci={a1+b1,max{ci−1,∑j=1iaj}+bi,if i = 1if 2≤i≤n 当然,吝啬的皇后并不希望太多的奖金被发给大臣,所以她想请你来重新安排一下队伍的顺序,使得获得奖金最多的大臣,所获奖金数目尽可能的少。 注意:重新安排队伍并不意味着一定要打乱顺序,我们允许不改变任何一位大臣的位置。
第一行包含一个正整数 T,表示测试数据的组数。 接下来 T 个部分,每个部分的第一行包含一个正整数 n,表示大臣的数目。 每个部分接下来 n 行中,每行两个正整数,分别为 ai和 bi,含义如上文所述。
共 T 行,每行包含一个整数,表示获得奖金最多的大臣所获得的奖金数目。
1 3 4 1 2 2 1 2
8
2 5 85 100 95 99 76 87 60 97 79 85 12 9 68 18 45 52 61 39 83 63 67 45 99 52 54 82 100 23 54 99 94 63 100 52 68
528 902
与上题类似,我们此题仍然先考虑相邻两项 i , j i,j i,j 先考虑交换 i , j i,j i,j对前后的影响 若交换 i , j i,j i,j使 m a x { w i , w j } max\{w_i,w_j\} max{wi,wj}变小: i , j i,j i,j前的元素不受影响, i , j i,j i,j后的元素情况不会变差,且可能产生更好情况 所以此题可以用邻项交换排序来解决 现在讨论我们比较的方式,设 k = ∑ p m i n { i , j } − 1 a p ; k =\sum^{min\{i,j\}-1}_p{a_p}; k=∑pmin{i,j}−1ap; i , j 前 一 位 大 臣 的 收 益 为 p r e i,j前一位大臣的收益为pre i,j前一位大臣的收益为pre 因为 b i b_i bi为正数,所以 c i c_i ci单调递增 若 i i i在 j j j前, m a x { w i , w j } = w j = m a x { k + a i + a j , m a x { p r e , k + a i } + b i } + b j max\{w_i,w_j\} = w_j = max\{k+a_i+a_j,max\{pre,k+a_i\}+b_i\}+b_j max{wi,wj}=wj=max{k+ai+aj,max{pre,k+ai}+bi}+bj 若 j j j在 i i i前, m a x { w i , w j } = w i = m a x { k + a j + a i , m a x { p r e , k + a j } + b j } + b i max\{w_i,w_j\} = w_i = max\{k+a_j+a_i,max\{pre,k+a_j\}+b_j\}+b_i max{wi,wj}=wi=max{k+aj+ai,max{pre,k+aj}+bj}+bi 所以 i i i在 j j j前的条件为 m a x { k + a i + a j , m a x { p r e , k + a i } + b i } + b j < m a x { k + a j + a j , m a x { p r e , k + a j } + b j } + b i max\{k+a_i+a_j,max\{pre,k+a_i\}+b_i\}+b_j < max\{k+a_j+a_j,max\{pre,k+a_j\}+b_j\}+b_i max{k+ai+aj,max{pre,k+ai}+bi}+bj<max{k+aj+aj,max{pre,k+aj}+bj}+bi 因为 m a x { x , y } + z = m a x { x + z , y + z } max\{x,y\}+z = max\{x+z,y+z\} max{x,y}+z=max{x+z,y+z} 所以不等式左右两边化为 m a x { k + a i + a j + b j , p r e + b i + b j , k + a i + b i + b j } max\{k+a_i+a_j+b_j,pre+b_i+b_j,k+a_i+b_i+b_j\} max{k+ai+aj+bj,pre+bi+bj,k+ai+bi+bj}和 m a x { k + a i + a j + b i , p r e + b i + b j , k + a j + b i + b j } max\{k+a_i+a_j+b_i,pre+b_i+b_j,k+a_j+b_i+b_j\} max{k+ai+aj+bi,pre+bi+bj,k+aj+bi+bj} 因为两边都有 p r e + b i + b j pre+b_i+b_j pre+bi+bj所以需要比较的元素是 m a x { k + a i + a j + b j , k + b i + b j + a i } max\{k+a_i+a_j+b_j,k+b_i+b_j+a_i\} max{k+ai+aj+bj,k+bi+bj+ai}和 m a x { k + a i + a j + b i , k + b i + b j + a j } max\{k+a_i+a_j+b_i,k+b_i+b_j+a_j\} max{k+ai+aj+bi,k+bi+bj+aj}两边同减去 k + a i + a j + b i + b j k+a_i+a_j+b_i+b_j k+ai+aj+bi+bj并化简得到 m i n { a j , b i } > m i n { a i , b j } min\{a_j,b_i\} > min\{a_i,b_j\} min{aj,bi}>min{ai,bj} 利用这个结论,我们可以写出下面这个假标程 标程
但是,这个程序叫上去会有部分点WA,下面提供一组hack数据
2 4 1 1 1 1 3 5 2 7 4 1 1 3 5 2 7
16 17
但是我们发现,这两组数据只有大臣的顺序是不同的 如果我们输出排序后的结果,则是
1 1 1 1 2 7 3 5
和
1 1 3 5 1 1 2 7
为什么会这样,我们得先了解严格弱序
而 C++ 标准库要求用于排序的运算符必须满足严格弱序,下面是The c++ standard library对严格弱序的描述:
(1)It has to be antisymmetric. This means that for operator <: If x<y is true, then y<x is false. This means that for a predicate op(): If op(x,y) is true, then op(y,x) is false. (2)It has to be transitive. This means that for operator <: If x<y is true and y<z is true, then x<z is true. This means that for a predicate op(): If op(x,y) is true and op(y,z) is true, then op(x,z)is true. (3)It has to be irreflexive. This means that for operator <: x<x is always false. This means that for a predicate op(): op(x,x) is always false. (4)It has to have transitivity of equivalence, which means roughly: If a is equivalent to b and b is equivalent to c, then a is equivalent to c. This means that for operator <: If !(a<b)&&!(b<a) is true and !(b<c)&&!(c<b) is true.then !(a<c)&&!(c<a) is true. This means that for a predicate op(): If op(a,b), op(b,a), op(b,c), and op(c,b) all yield.false, then op(a,c) and op(c,a) yield false.
根据定义,简单的说就是我们上述比较命题没有严格弱序的特征——它不满足上述定义中的传递性 也就是说根据 m i n { a i , b j } < m i n { a j , b i } min\{a_i,b_j\} <min\{a_j,b_i\} min{ai,bj}<min{aj,bi}和 m i n { a j , b k } < m i n { a k , b j } min\{a_j,b_k\} <min\{a_k,b_j\} min{aj,bk}<min{ak,bj}不能推出 m i n { a i , b k } < m i n { a k , b i } min\{a_i,b_k\}<min\{a_k,b_i\} min{ai,bk}<min{ak,bi} 反例应该很好举吧
由于证明较为复杂,蒟蒻不太懂,详见大佬博客
观察不等式 m i n { a j , b i } > m i n { a i , b j } min\{a_j,b_i\} > min\{a_i,b_j\} min{aj,bi}>min{ai,bj}我们发现 i i i若在 j j j前面,则 a i a_i ai越小越好, a j a_j aj越大越好 这样,我们想到来讨论 a i a_i ai和 b i b_i bi的大小关系,我们将这些数据分为三组: (1) a i < b i & & a j < b j a_i < b_i \space\space\&\&\space\space a_j < b_j ai<bi && aj<bj 按 a a a的升序排列即可 (2) a i = b i & & a j = b j a_i=b_i\space\space\&\&\space\space a_j=b_j ai=bi && aj=bj 怎么排都无所谓 (3) a i > b i & & a j > b j a_i > b_i \space\space\&\&\space\space a_j > b_j ai>bi && aj>bj 按 b b b的降序排列即可 组与组间又没有确定的大小关系呢 先取(1)(2)组元素进行比较设 i i i为第一组元素, j j j为第二组元素 比较对象为 m i n { a j , b i } min\{a_j,b_i\} min{aj,bi}和 m i n { a i , b j } min\{a_i,b_j\} min{ai,bj} 将上述条件带入该式得到 m i n { a j , b i } ≥ m i n { a i , b j } min\{a_j,b_i\} \geq min\{a_i,b_j\} min{aj,bi}≥min{ai,bj} 所以第一组元素在第二组元素前 同理可得第二组元素在第一组元素前 这样就可以得到我们的满分代码
(1)在可以通过比较相邻两项得出交换或不交换一定不会更差时,可以通过邻项交换排序的方式来得到最优解。
(2)邻项交换排序的比较函数需要满足严格弱序,并且排序完成后任意交换相邻元素都不会更优。
(3)使用这种算法时,一定要注意以上两点,才能得到真正正确的算法。
