算法竞赛进阶指南_打卡_题解_0x20
①:小猫爬山
https://www.acwing.com/problem/content/description/167/
索道上的缆车最大承重量为 W,而 N 只小猫的重量分别是 C1、C2……CN。
当然,每辆缆车上的小猫的重量之和不能超过 W。
每租用一辆缆车,翰翰和达达就要付 1 美元,所以他们想知道,最少需要付多少美元才能把这 N 只小猫都运送下山?
\(1≤N≤18, 1≤Ci≤W≤108\)
一看数据范围,我们必然会去打暴力。
就有如下两个方法:
①:爆搜+剪枝:
剪枝1:我们先对小猫进行降序排序,我们就可以得到一个单调性。在dfs时,我们便可以从上一个枚举的位置开始枚举,减少时间复杂度。由于A+B=B+A,对其逆序排序后从上一个枚举的位置找起,也能减小搜索树的大小。
剪枝2:可行性剪枝,如果当\(前重量<上限重量\),则可以选择放入或者另开缆车。否则另开一辆缆车。
注:我们以缆车数量看成一个最主要的评估状态,而不要以猫有无放入为主要评估状态,不然会经过很多重复的子状态,导致像我一样超时半天。
②:二进制枚举+递推:
这个就是我最初ac的想法了。
我们二进制枚举每一个可能出现的状态,二进制位为1时为该猫已上车,0则相反。
我们有二元组数组state,state[i]存储为i状态时最小需车量(cnt)及当前车最小承重量(w)。
我们有以下递推公式:
如果pre状态可以由i状态加入第j只重量为a[j]的小猫转移过来:
若 state[i].w+a[j]<w
若 state[i].w+a[j]<state[pre].w且state[i].cnt\(==\)state[pre].cnt
则 state[pre]={state[pre].cnt,state[i].w+a[j]}
若 state[i].cnt<state[pre].cnt
则 state[pre]={state[i].cnt,state[i].w+a[j]}
否则
若 state[pre].cnt>state[i]+1
则 state[pre]={state[i].cnt,state[i].w+a[j]}
若 state[pre].cnt\(==\)state[i]+1
则 state[pre]=
②:数独:剪枝,搜索,位运算
数独是一种传统益智游戏,你需要把一个 9×9 的数独补充完整,使得图中每行、每列、每个 3×3 的九宫格内数字 1∼9 均恰好出现一次。
请编写一个程序填写数独。
这题的剪枝策略主要是按一定规律枚举数独的格子。
我们考虑将搜索树减小,所以我们要尽可能的少枚举点。因此我们可以优先枚举可以填充更少种类数字的格子。
在每一轮枚举中,为了将我们数独中每个格子能够填充的数字尽可能少,因此我们每轮将唯一能够确定的格子先填上。
在上述剪枝过后时间效率大大提高了,可是还是超时,这里我们就要对其常数进行优化,我们采用位运算的方法。
我们存在一个数组st[i][j],i为1,2,3分别代表行,列,从左到右从上到下的3*3小九宫格。j代表所属的第j行,列,小九宫格。
其利用九位二进制数存储该数是否能被取。有\(k=st[1][x]\)&\(st[2][y]\)&\(st[3][(x-1)/3*3+y\) \(mod\) \(3]\)
当k!=0,则存在能取的数。
当k上的二进制位只有一个1时,能够填的数唯一确定。(我们可以用lowbit运算快速求出有几位1)
\(******\)
注:位运算效率为n/32,n为二进制的位数,也就是会比原程序快上30倍左右。
③:木棒:搜索,剪枝
https://www.acwing.com/problem/content/169/
乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过 50 个长度单位。
然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。
请你设计一个程序,帮助乔治计算木棒的可能最小长度。
每一节木棍的长度都用大于零的整数表示。
第一行是一个不超过 64 的整数,表示砍断之后共有多少节木棍。
我们首先设立一个st数组,表示该木棍是否被使用过。
然后我们考虑剪枝:
①:优化枚举顺序:和小猫爬山一样,我们可以降序枚举木棍。在降序的木棍长度序列中,很明显的,当存在一个木棍集A,且A.len<lim,使A.len\(==\)lim时,使用一个木棍一定优于使用两个木棍,因此在存在A.len\(==\)lim时,我们只需要枚举一次即可return,以免等效的搜索树出现。
②:剪去等效搜索树:当我们选择长度为a[i]的木棍时,若此次搜索的结果为false,这其余本次使用长度为a[i]的搜索树必然也为false,因此当我们枚举到长度为a[i]时可以continue掉。
③:这里还有一个很强的剪枝:因为我们降序枚举,当其最终枚举到了false,那么就意味着当前枚举出长度为lim的木棍集顺序不对。因此我们可以直接return当前的新木棍(tot=0)或者上一步的最后木棍(tot+a[j]=lim)。且一定return false。
④:生日蛋糕:推公式,剪枝,搜索开始不会了
https://www.acwing.com/problem/content/170/
ACM-THU 为此要制作一个体积为 Nπ 的 M 层生日蛋糕,每层都是一个圆柱体。
设从下往上数第 i 层蛋糕是半径为 Ri,高度为 Hi 的圆柱。
当 i<M 时,要求 Ri>Ri+1 且 Hi>Hi+1。
由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积 Q 最小。
令 Q=Sπ ,请编程对给出的 N 和 M,找出蛋糕的制作方案(适当的 Ri 和 Hi 的值),使 S 最小。
除 Q 外,以上所有数据皆为正整数。
\(1≤N≤10000, 1≤M≤20\)
首先不要像我读了假题。连暴力都写错
蛋糕长这样:
而不是这样:
朴素的想,我们一定会去枚举每一层的高度(h[i])和半径(r[i])。
有圆柱体体积公式\(N=\sum_{1}^{m} h[i]*r[i]*r[i]\),我们记当前(第j层时)已经积累的体积为v,当前已经积累的面积为s。
体积也可以化简为\(N=v+\sum_{j}^{m} h[i]*r[i]*r[i]\),且记\(H=max(h[j]...h[m]),R=max(r[j]...r[m])\)
又因为\(R_{i}>R_{i}+1 且 H_{i}>H_{i}+1\)
所以\(r[j-1]>R,h[j-1]>H\)。
因此\(N-v\lt (h[j]*r[j]*r[j])*{m-j}\)
我们首先枚举h再枚举r,所以有:
\(r[j]\in [j,min({\sqrt{N-v}},r[j-1]-1)]\)(此时h[j]看做常量)
\(h[j]\in [j,min((N-v)/r[j]^2,h[j-1]-1)]\)
目前为止,我们就已经确定h,r枚举范围了。
接下来,我们对其进行剪枝:
①:可行性剪枝:如果当前的体积加上未来可能的最小体积大于题目所给出的体积,必然无解。
什么时候是最小体积?必然是每一个r[j],h[j]均最小,即为j时,我们可以预处理出来,作为剪枝。
②:最优行剪枝:如果当前表面积加上未来可能最小表面积大于目前已知最小面积,必然无解。
同最小体积预处理出来最小面积。
③:最重要的最优性剪枝:我推八出来,我爬爬
第三个剪枝是最重要的,也是最难推的()
⑤:送礼物:双向搜索,二进制枚举,二分
https://www.acwing.com/problem/content/173/
达达帮翰翰给女生送礼物,翰翰一共准备了 N 个礼物,其中第 i 个礼物的重量是 G[i]。
达达的力气很大,他一次可以搬动重量之和不超过 W 的任意多个物品
达达希望一次搬掉尽量重的一些物品,请你告诉达达在他的力气范围内一次性能搬动的最大重量是多少。
\(1≤N≤46, 1≤W,G[i]≤2^{31}−1\)
我们很容易能联想到背包问题八。
不过这道题是大背包问题,无法像我们平时接触过的背包问题在多项式时间内解决。
如果考虑动态规划,背包体积过大,内存爆了,\(O(n*w)\)的时间复杂度也爆了。
我们考虑朴素的搜索。
时间复杂度\(O(2^{46})\),必然也爆。
但是我们可以这么来想。我们将背包的物品分为数量接近的两组,不需要依据任何关键字。
我们可以预处理A组的物品价值,我们在搜索B组的物品的组合时只要在A预处理出来的价值二分查找找到重量小于等于(W-当前重量)即可。
时间复杂度\(O(n2^n)\)。
不过这道题比较恶心人,它只能用dfs过,不能二进制枚举过,会卡二进制枚举。
\(******\)
注:我校OJ上竞赛训练——动态规划——巨大背包问题,就是这个板子题但是我wa麻了,不知道锅在那了
⑥:噩梦:双向BFS,玄学剪枝
https://www.acwing.com/problem/content/179/
给定一张 N×M 的地图,地图中有 1 个男孩,1 个女孩和 2 个鬼。
字符 . 表示道路,字符 X 表示墙,字符 M 表示男孩的位置,字符 G 表示女孩的位置,字符 Z 表示鬼的位置。
男孩每秒可以移动 3 个单位距离,女孩每秒可以移动 1 个单位距离,男孩和女孩只能朝上下左右四个方向移动。
每个鬼占据的区域每秒可以向四周扩张 2 个单位距离,并且无视墙的阻挡,也就是在第 k 秒后所有与鬼的曼哈顿距离不超过 2k 的位置都会被鬼占领。
注意: 每一秒鬼会先扩展,扩展完毕后男孩和女孩才可以移动。
求在不进入鬼的占领区的前提下,男孩和女孩能否会合,若能会合,求出最短会合时间。
\(1<n,m<800\)
不要读了假题,男性是一秒可以移动三步,每一步的方向都可以不一样。
我记得假期集训讲过这个题的,双向BFS感觉还是挺形象的。
我们分别从起点和终点开始搜,每次将一层完全扩展完毕。当存在一点男和女都经过,则返回结果。
所以我们在一个BFS里面存储两个队列,一个是男的队列,另一个是女的队列。
每一次我们将男性所有\(step\)为\(t\)的扩展到\(t+1\),女性同理。
最后得到的答案会减小很多冗余步数。
但是我交上去的代码超时了,咋整?
因为男性可以一秒移动三步,所以我们对于男性的起点与终点查看他的可达性。
因此我们再做一次BFS得出男性的可达性。
就这在一步我们超时了,我们有两种优化方法。
①:我们不考虑终点,我们只对起点做一次BFS(要求所有扩展的点与起点曼哈顿距离不超过3)
将所有可达点放入男性队列中。
②:我们考虑终点,因为起点与终点的曼哈顿距离不超过3,因此每一步移动一定会使当前点到终点的曼哈顿距离变小。
所以我们可以多加一个曼哈顿距离是否变小的判断,时判断的复杂度变为\(\log n\)(n为起点到终点的曼哈顿距离范围内所有点数),如果可达将该终点放入男性队列中。
⑥:第K短路:astar,板子题
https://www.acwing.com/problem/content/180/
给定一张 N 个点(编号 1,2…N),M 条边的有向图,求从起点 S 到终点 T 的第 K 短路的长度,路径允许重复经过点或边。
注意: 每条最短路中至少要包含一条边。
输入格式
第一行包含两个整数 N 和 M。
接下来 M 行,每行包含三个整数 A,B 和 L,表示点 A 与点 B 之间存在有向边,且边长为 L。
最后一行包含三个整数 S,T 和 K,分别表示起点 S,终点 T 和第 K 短路。
\(1≤S,T≤N≤1000, 0≤M≤105, 1≤K≤1000, 1≤L≤100\)
astar的典型应用了,这里就讲一下主要思路八。
求最短路,我们肯定首先会想到dij。
为什么dij能够求出最短路?
因为从源点U到汇点V如果存在一条路径,则一定存在一条最短路径。
我们贪心的枚举每一个从点U出发的路径,利用堆的数据结构。
我们一定能将每一条路径从小到大枚举完毕。如果第一次出现存在一条路径的终点正好为V出队,这该路径为最短路径。
不过对于次短路,第k短路。好像,dij明显不能在很好的时间内处理完毕。
但是根据上面的结论 “如果第一次出现存在一条路径的终点正好为V出队,这该路径为最短路径”
那么当终点为V的路径出队k次不就为第k短路径了吗?
一算复杂度\(O(k*(n+m)*log(n+m))\),铁炸。
我们考虑优化算法,如果每一次我们都能更逼近U这个点,并且每一次出队都是保证由最小到最大变化。
那么优化时间复杂度是可以实现的,这就意味着我们要转变我们的贪心策略。
在跑dij的过程中,可能扩展了许多不属于答案的对象,但是在途中它的权值确实很低,被加入了队列当中。
我们考虑减少这种对象的入队。
我们可以设计一个函数(也可以是一个常量),我们称之为\(\Large估值函数\)。
因为点U到点V的每一个路径的最短路小于等于\(f[U][j]+f[j][V]\)。
我们记j为中转节点。
我们可以预先处理出一些\(f[U][j]+f[j][V]\)的大小,在后续中所有\(f[U'][j']+f[j'][V']>f[U][j]+f[j][V]\)
前者被处理的优先级一定比后者低,因为后者更可能接近答案。并且上述不影响出队的大小变化,因此可以采用。
对于我们第k短路问题,我们可以采用相同的策略。
我们反向跑dij,得出汇点V到每一个点的最短距离,以此作为估价函数。
在astar中,我们要进行上述优化操作。
如果一条路径从U到V的为第k短路径,则点V一定被出队过k次(包括本次)。
因此如果从点U到V'途经V的第k短路径,路径上的每一个点一定没有被出队大于等于k次(终点除外)。
我们反向处理最短路径,可以得到每一个点到汇点的最短距离。
我们以\(dist[V']+f(V')\)为关键字,对堆进行排序。(dist[V']为源点到点V'的距离,f(V')为点V'到汇点的最短距离)
当堆中当前点为汇点出队k次时,一定为该路径的第k小路径。
⑦:八数码:astar,双向BFS,杂项
https://www.acwing.com/problem/content/181/
在一个 3×3 的网格中,1∼8 这 8 个数字和一个 X 恰好不重不漏地分布在这 3×3 的网格中。
例如:
1 2 3
X 4 6
7 5 8
在游戏过程中,可以把 X 与其上、下、左、右四个方向之一的数字交换(如果存在)。
我们的目的是通过交换,使得网格变为如下排列(称为正确排列):
1 2 3
4 5 6
7 8 X
例如,示例中图形就可以通过让 X 先后与右、下、右三个方向的数字交换成功得到正确排列。把 X 与上下左右方向数字交换的行动记录为 u、d、l、r。
现在,给你一个初始网格,请你通过最少的移动次数,得到正确排列。
为什么是杂项呢,这里有一个我目前还没看到严格数学证明的结论:
如果八数码存在奇数个逆序对,一定无解,否则一定有解。(先贴在这里,看下以后有没有看到严格证明)
astar有一个特别重要的特征:
通过修正枚举的顺序,大大提高检索答案的效率。
如何修正?一般与堆排序的关键字最大值最小值有关。
如果每一次操作都是正确的,每个数个数的位置与它的正确位置曼哈顿距离之差之和所用次数一定小于等于真正移动次数。
也就是每个数个数的位置与它的正确位置曼哈顿距离之差之和”为最小移动次数。
因此估价函数就为“每个数个数的位置与它的正确位置曼哈顿距离之差之和”。
如果我们要去求最小移动次数,每个数个数的位置与它的正确位置曼哈顿距离之差之和也应当最小才能尽可能最优。
\(******\)
注意到这道题给了我们初态和末态,我们不妨也可以用双向BFS做。
复杂度从\(O(不知道)\)变成了\(O(不知log_{道})\)
当然,astar是最优解了。
⑧:排书:IDA,双向BFS
https://www.acwing.com/problem/content/182/
给定 n 本书,编号为 1∼n。
在初始状态下,书是任意排列的。
在每一次操作中,可以抽取其中连续的一段,再把这段插入到其他某个位置。
我们的目标状态是把书按照 1∼n 的顺序依次排列。
求最少需要多少次操作。
如果最少操作次数大于或等于 5 次,则输出 5 or more。
\(1≤n≤15\)
因为最小操作次数大于等5时直接不输出正解,因此我们可以考虑迭代加深。
但是,如果只是普通的迭代加深复杂度为\(O(n^{12})\)很难不爆。
因此我们要引入astar的思想优化算法,减小搜索树的分支。
很明显如果有 \(当前深度+未来求解答案需要深度>limit\) 则一定无解。
因此我们可以设计一个估值函数f()。
下面是神必的推理过程(我看不懂,我爬)
估价函数需要满足:不大于实际步数
在最终状态下,每本书后面的书的编号应该比当前书多1。
每次移动最多会断开三个相连的位置,再重新加入三个相连的位置,因此最多会将3个错误的连接修正,所以如果当前有 \(tot\) 个连接,那么最少需要 \(⌈tot/3⌉\) 次操作。
因此当前状态 \(s\)的估价函数可以设计成 \(f(s)=⌈tot/3⌉\)。
接着就是很常见的搜索代码贴去就行了。
⑨:涂满它!:IDA
https://www.acwing.com/problem/content/196/
在游戏开始时,系统将随机生成 N×N 的方形区域,并且区域内的每个网格都被涂成了六种颜色中的一种。
玩家从左上角开始游戏。
在每个步骤中,玩家选择一种颜色并将与左上角连通的所有格子(包括左上角)都变成该种颜色。
这里连通定义为:两个格子有公共边,并且颜色相同。
通过这种方式,玩家可以从左上角开始将所有格子都变为同一种颜色。
下图显示了 4×4 游戏的最早步骤(颜色标记为 0 到 5):
请你求出,给定最初区域以后,最少要多少步才能把所有格子的颜色变成一样的。
\(2≤N≤8\)
这道题是不是特别像之前训练赛的题目。不过这道题需要我们求解答案,因此搜索树的范围大了很多。
注:\(******\)
不要学我用并查集,常数巨大,T我半天,直接维护得了。
因为我们要求存在正解的最小深度,因此迭代加深一定是可以想到的。
在看题干要求和数据范围,暴力就可以晾一边了。
对于迭代加深的优化,好像就只有IDA,因此我们从IDA入手该题。
起初我是这样想到,如果图中存在n个连通块,那么就以n为估价函数。
开始行不通,只过了样例。仔细一想,就算存在n个连通块,每一次合并都可能合并大于等于一个连通块。
因此以连通块的数量为估价函数的想法就不能成立了。
如果以除\((1,1)\)的连通块之外,存在n个不同的数字,那么就可以以n为估价函数。
因为每一次合并只能合并一种颜色,因此全部合并最少需要n次。虽然暴力了一点,但是所幸结论是正确的。
之后就是一点复杂的模拟了,我们需要维护\((1,1)\)连通块的所有集合,与\((1,1)\)连通块相邻的所有连通块,每一次合并时的回溯。
注\(******\):
不要用并查集,不要用并查集!我是前车之鉴!
⑩:骑士精神:IDA
https://www.acwing.com/problem/content/197/
在一个 5×5 的棋盘上有 12 个白色的骑士和 12 个黑色的骑士,且有一个空位。
在任何时候一个骑士都能按照骑士的走法(它可以走到和它横坐标相差为 1,纵坐标相差为 2 或者横坐标相差为 2,纵坐标相差为 1 的格子)移动到空位上。
给定一个初始的棋盘,怎样才能经过移动变成如下目标棋盘:为了体现出骑士精神,他们必须以最少的步数完成任务。
和之前的几道题一样,一看就知道是IDA。
起初我是这样想:
①:每一次最多能使两个棋子位置正确,其后为一定次数的一次。直到某次,有开始这个循环。
因此我们的估价函数就是每一个棋子不对应终态的个数。(至少需要这么多步使其排列正确)
②:我们考虑记忆化搜索,如果某个状态被搜索过,就不应该再次搜索,减小搜索树范围。
我们给每一个棋子从0到24标号,st矩阵存储某一个棋子是否到达该点,如果编号为k的棋子已经到过该点\((i,j)\)。
则 ((st[i][j]>>)k&1)==1 利用状态压缩的思想得到。
很不幸的是最后一个点没有过。
但是当我删掉st矩阵的时候AC了。。。
根本没有必要状压啊。。。状压就没有正确性了啊。
就算一个点已经被第k个棋子访问过,再次访问仍有可能是最优解。