Zplhz智破连环阵.docx
- 文档编号:30510194
- 上传时间:2023-08-16
- 格式:DOCX
- 页数:11
- 大小:78.59KB
Zplhz智破连环阵.docx
《Zplhz智破连环阵.docx》由会员分享,可在线阅读,更多相关《Zplhz智破连环阵.docx(11页珍藏版)》请在冰豆网上搜索。
Zplhz智破连环阵
NOI’2003-Day2-Problem3
智破连环阵(Zplhz)
——朱泽园
[问题描述]
B国在耗资百亿元之后终于研制出了新式武器——连环阵(ZenithProtectedLinkedHybridZone),并声称这是一种无敌的自发性智能武器。
但A国经侦察发现,连环阵其实是由M个独立武器组成的。
这M个武器编号为1,2,…,M。
每件武器有两种状态:
无敌自卫状态和攻击状态。
最初,1号武器处于攻击状态,其他武器都处在无敌自卫状态。
以后,一旦第i(1i 当第M号武器被消灭以后,这个造价昂贵的连环阵就被彻底摧毁了。 为了打败B国,A国军事部长打算用最廉价的武器——炸弹来消灭连环阵。 经过长时间的精密探测,A国的军事家们掌握了连环阵中M个武器的平面坐标,然后依此选择了n个点,并在这些点上安放了特殊的定时炸弹。 这n个炸弹编号为1,2,…,n。 每个炸弹的作用半径均为k,且会持续爆炸5分钟。 在这5分钟内,每枚炸弹都可以在瞬间消灭离它直线距离不超过k的、处在攻击状态的B国武器。 和连环阵类似,最初a1号炸弹持续引爆5分钟时间,然后a2号炸弹持续引爆5分钟时间,接着a3号炸弹引爆……以此类推,直到连环阵被摧毁。 在每个炸弹爆炸的时候,其它尚未引爆的炸弹都处于地下隐蔽处,不会被己方的炸弹摧毁。 显然,选好a1、a2、a3...十分重要。 好的序列可以在仅使用较少炸弹的情况下就能将连环阵摧毁;坏的序列可能在使用完所有炸弹后仍无法将连环阵摧毁。 现在,请你决定一个序列a1、a2、a3…使得在第ax号炸弹引爆的时间内连环阵被摧毁。 这里的x应当尽量小。 输入文件 输入文件zplhz.in第一行包含三个整数: M、n和k(1M,n100,1k1000),分别表示B国连环阵由M个武器组成,A国有n个炸弹可以使用,炸弹攻击范围为k。 以下M行,每行由一对整数xi,yi(0xi,yi10000)组成,表示第i(1iM)号武器的平面坐标。 再接下来n行,每行由一对整数ui,vi(0ui,vi10000)组成,表示第i(1in)号炸弹的平面坐标。 输入数据保证无误和有解。 测试数据中的xi、yi、ui、vi是随机生成的。 输出文件 输出文件zplhz.out的第一行包含一个整数x,表示实际使用的炸弹数。 第二行包括x个整数,依次表示a1,a2,…,ax。 样例输入1 436 06 66 60 00 15 03 11 样例输出1 2 13 样例输入2 101045 4167 340 6924 7858 6264 545 8127 6191 9542 2736 914 253 9282 2116 1895 4726 7138 6912 6799 3594 样例输出2 5 62134 评分标准 对每个测试点,如果你的输出合法,评分公式如下: 其中good为我们的参考解,ans为你的答案。 如果你的输出不合法,则该测试点只能得0分。 总分的计算公式: 其中scorei为你第i个测试点的得分。 注意: 这道题的总分有可能超过100分。 [问题简述] 给出M个武器和n个炸弹,并且知道每个炸弹可以攻击哪些武器。 我们需要按照武器的编号顺序消灭它们,但是炸弹引爆之后只能消灭连续编号的武器,并且不能重复使用。 求一个最少的炸弹使用序列。 测试数据是随机生成的。 [问题分析] 观察评分方式不难得知,这题应该是一个NP题。 对于NP问题有什么好算法呢? 贪心 每次我们选取一个炸弹,炸去尽可能多的目标。 这个算法显然是错的,而且有时候会导致无法炸掉所有的目标,比如: 每行代表一个炸弹,七列依次代表七个武器,实心代表某个炸弹可以攻击某个目标。 那么如果贪心,我们首先会选择第一个炸弹,接着,就根本无法炸去第7个目标,而正确的使用序列为: 2、3、1。 我们可以对其进行小优化,不停地判断剩下的目标是否可以被完全覆盖,如果没有完全覆盖,就选择次优的……但是终究无法改变拿低分的命运。 深度优先搜索 搜索确实是一种无奈之下的选择,深度优先搜索就是把所有的情况都考虑进去,找出最优的。 可以进行的优化有两种: 1、分枝定界、A*搜索。 在正式搜索之前,进行一次预先的贪心处理,尽可能地缩小搜索范围。 每次将当前状态,以及接下来达到目标所最少需要的炸弹数(估值)相加,如果大于等于当前的最少炸弹数,那么就剪枝。 2、即使这样,遇到了大数据还是铁定超时,为了让程序在时限内出界,可以适当卡结点。 就是已经处理的情况数量,大于一个固定值的时候,输出当前较优值并且退出。 可惜这种算法,仍然无法得到一个较好的分值。 宽度优先搜索——IDA*搜索 为了尽可能缩小处理的情况总数,我们自然而然会想到宽度优先搜索。 因为宽度优先搜索从优到劣地对序列进行扩展,对于所有比最优情况劣的状态,都是不会被扩展出来的。 但是这一道题目会出现数量级别高达O(N! )的状态,空间显然是承受不住的。 此时我们选用DFSID搜索。 ID就是IterativeDeepening,说白了也就是不断地将深度进行加深,刚开始搜索深度为1的所有状态,如果没找到解,就搜索深度为2的所有状态……这个算法在一定程度上是BFS搜索的一种DFS形式扩展,空间复杂度被降低了,时间复杂度却不会上升太高。 在这种搜索中,我们可以预先用A*的估值函数算出下界再开始深度加深,另外仍然可以使用分值定界A*,以及卡结点的优化方法。 但是结果仍然不理想(参见“程序结果”) 冒险优化 注意到“问题简述”中,我将“测试数据是随机生成的”这句话加了下划线! 搜索时,把所能够扩展的状态,按照所能炸掉目标的数量从大到小排序。 如果每一次我们都选择最大的(局部利益最优的),那么这就是一个贪心过程。 在随机生成的数据中,分析一个最优的使用炸弹的序列,它在每一步所选取的是局部利益第几优的,不难发现大部分都是选取局部最优的! 这给我们什么启发呢? 我们是不是能限定每次只选取局部利益前X优的进行搜索呢? 这样程序的复杂度从O(N! )降到了O(X^N)级别,而且大多数情况下可以剪枝,是很有效果的! (如果X选取2,那么测试数据中有一个点得9分,一个点得11分,还有一个点超时) 仔细分析一下可以知道,选取“局部利益最优”的步骤还是非常之多的,如果每一步我们都考虑“次优局部利益”,着实还是一种浪费! 另外,X取2也不是很符合实际,如果要拿满分甚至更多的分,势必要把X设定为3! 这时候我想到一个限定选取“次优局部利益”次数的算法。 我们限定选取“次优局部利益”的总次数上限为MIDDLE_NODE_COUNT,限定选取“第三优局部利益”的总次数上限为LARGE_NODE_COUNT。 因为最多M个目标,所以MIDDLE_NODE_COUNT取8左右比较合适,LARGE_NODE_COUNT取2左右比较合适。 总体比较与感想 这道题目的标准数据是两个搜索程序综合而得的,而且运行了相当长的时间才出结果。 在考场上的我们,只有6sec的时间限制,强力冒险剪枝自然是必不可少的。 我在本题中使用的冒险剪枝是非常有效并且典型的。 在搜索超时的情况下,大多数选手会选择卡时,或者卡结点的方法。 然而往往这种情况下,只搜索了搜索树的一小部分: 而我这样的贪心式搜索,可以覆盖搜索树的大部分地方,并且都是较优值! [程序] 数据结构&算法设计 #defineSMALL_NODE1 #defineMIDDLE_NODE2 #defineLARGE_NODE3 #defineMIDDLE_NODE_COUNT8 #defineLARGE_NODE_COUNT2 代表前1~SMALL_NODE优的局部状态一定搜索,前SMALL_NODE+1~ MIDDLE_NODE优的局部状态搜索MIDDLE_NODE_COUNT个,前 MIDDLE_NODE+1~LARGE_NODE优的局部状态搜索LARGE_NODE_COUNT个。 intlast[MaxN][MaxN]; last[i][j]计算第i个炸弹炸第j个目标后,可以持续炸到第多少个目标 intdis(intx1,inty1,intx2,inty2) 返回坐标距离的平方 voidInit() 读入数据并且生成last数组,last生成完毕以后,各对象的坐标等参数都不需要了。 intmaxd; inthash[MaxN],ans[MaxN],now[MaxN]; maxd记录当前最大深度,hash记录被用过的炸弹,now记录当前炸弹序列,ans记录最优炸弹序列(用于输出) structTNext { intv,last; }; 记录局部的所有可转移的方案,v表示使用的炸弹编号,last表示可以出炸到多少号目标。 intlower_evaluate(intkilled) A*估值函数,killed为当前炸毁的目标数量,返回至少需要的炸弹数量。 intsearch(intLarge_Node_Count,intMiddle_Node_Count,intkilled,intused) 当前剩下的使用“次优”和“第三优”局部状态的数量,killed为当前摧毁的目标数,used为当前使用的炸弹数。 voidDoit() 主过程,进行深度的叠加 程序清单 (使用1024*768分辨率+WindowsNotepad可达到视觉最佳效果) Zplhz.cpp 程序结果 测试点 1 2 3 4 5 我的程序①C++ 0.0000s 0.0000s 0.0000s 0.0000s 0.0000s 我的程序②C++ 0.00s 0.00s 0.00s 0.00s 0.00s 我的程序得分 10 10 10 10 10 标程一①C++ 0.0000s 0.0000s 0.0549s 0.0000s 0.0000s 标程一得分 10 10 10 10 5 标程二①C++ 0.0000s 0.0000s 0.0000s 0.0000s 0.0000s 标程二得分 10 10 9 9 8 测试点 6 7 8 9 10 我的程序①C++ 0.0000s 0.0000s 0.0000s 0.0000s 2.2527s 我的程序②C++ 0.00s 0.00s 0.00s 0.00s 1.06s 我的程序得分 10 10 11 10 10 标程一①C++ 0.0549s 0.0549s 6.5934s TLE 8.4615 标程一得分 10 10 5 TLE 5 标程二①C++ 0.0000s 0.0000s 0.0000s 0.0000s TLE 标程二得分 7 10 9 9 TLE 时限: 6sec 这里我的程序是MIDDLE_NODE_COUNT取8,LARGE_NODE_COUNT取2的程序,如果MIDDLE_NODE_COUNT取7,第10个测试点只需要约0.5sec即可。 标程一是刘汝佳的IDA*程序,其中卡结点数为2000。 标程二是侯启明的深度优先A*搜索程序。 声明: 这两个程序不代表就是刘汝佳和侯启明的最终版本,原著是他们,是出自多时多手多地的一个中间版本。 TLE的点我等了1min也没有出结果就Ctrl+C了,这些比较说明我的这种近似算法单对这一道题来说是非常成功的。 测试环境与标准环境详见附录。 [总结] 在上海我没有想到IDA*搜索,使用了直接深度优先加上后面提到的冒险优化,可能是因为时间的关系,我也没有想到对“次优局部状态”的数量进行限制。 我最后的得分是63分,其中大部分(7个点)的分数都在8分以上,3个点超时。 超时是很不应该的,因为只要输出一个较优解,一般来说都能稳拿至少5分,这是我最大的失误所在! 我对NP问题的接触不多,各大ACM在线评测的网站上,也很少出现这样的题目,因为他们无法进行评测。 不过离开上海以后我想到的这种冒险优化,还是很有代表性的,应该也可以称得上是NP问题的克星。 我觉得在今后的训练道路上,一方面我要尽可能多接触NP问题,另一方面要让自己的思维和不理想的结果碰撞,产生火花! [附录] 测试环境与标准环境 测试环境① 编译器Djgpp3.2.1+Rhide1.4.9 -Wno-deprecated-O6-march=pentium3-ffast-math-fomit-frame-pointer Freepascal1.0.6-Op3 机型IntelCeleronprocessor735+128MBRAM 平台WindowsXPProfessional2002(5.1.2600)+DosforXP 测试环境② 编译器Djgpp2.953+Rhide1.4.7 -pipe-O6-march=pentium3-ffast-math-fomit-frame-pointer Freepascal1.0.6-Op3 机型IntelCeleronprocessor735+128MBRAM 平台DebianLinux3.0r0stable+KDE3.1r0 标准环境 编译器Djgpp2.953+Rhide1.4.9 Freepascal1.0.4 机型InterPentium1.4GHz 平台DebianLinux3.0
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Zplhz 连环
![提示](https://static.bdocx.com/images/bang_tan.gif)