分类目录归档:acm

[POJ 2079]

Triangle

旋转卡壳

题意:给出最多50000个点,可取任意3点作三角形,求这些三角形中得面积最大值。

首先,可以明确的是三角形的三个顶点必定在凸包上。求出凸包后,点的数量减少到O(n^2)算法可解。

然后,需要考虑的是最大面积三角形的三点会以何种形式出现。一开始考虑的是对于所有的对踵边,枚举其对应的所有三角形,但是WA。后来发现,三角形的三边可以都不为对踵边。应该枚举的是各个点对应的最大三角形面积。在求顶点i对应的最大三角形面积时,要依次求出当j取其余n-1个点时对应的最大三角形面积,因此,可以构造如下算法:

0. 初始化顶点i为第一个顶点:
1. 初始化j为i在凸包上的下一个顶点
2. 取k为使area(i,j,k)最大的顶点,更新最大面积
3. 当j!=i时,取j为下一个顶点,回到2.;否则取i为下一个顶点,若未取尽,回到1.

其中第3步计算k时,容易利用旋转卡壳的思想证明如下结论:边(i,j+1)对应的k,必定在边(i,j)对应的k之后。由此,对于某个i,其对应的j和k的总自增次数都是O(n)级别的,总体复杂度便是O(n^2)。

[POJ 2540]

Hotter Colder

半平面交

题意:在大小为10*10的矩形内有一个“最热点”,矩形内离最热点越远越冷。初始位置(0,0),移动50次,每次给出移动的目标位置以及相对之前位置的温度变化情况。对于每次移动,需要计算可能存在最热点的区域面积。

分析:初始整个矩形都是可行的,每次移动结果若是“Hotter”或者“Colder”,相当于将之前的可行区域进行了切分;若是“Same”则可行区域变为线段,面积恒为0。每次切分可以用半平面来表示。求面积时,由于题目数据量很小,依然可以O(n^3)地处理。具体做法是,求出每两条直线间的交点(包括初始矩形对应的4条边线),然后用各半平面方程判可行,得到的可行点便是可行区域的边界点。接下来,取它们的中心,再按极坐标排序,依次求中心与每两相邻点围成三角形的面积,最后求和即可。干净利落的1A解决。

[POJ 3384]

Feng Shui

半平面交

题意:给出一个凸多边形描述,问如何在凸多边形内放两个半径都为r的圆,使两圆覆盖区域并集的面积最大。两圆可以重叠,但重叠部分面积不累加。圆不能超出凸多边形的边界。题目保证有解,若有多解,输出任意一组。

根据题意,目标要使两圆重叠区域最小。很容易推出可行的圆心区域是原凸多边形各边向内“塌缩”r后围成的新多边形。那么,在这个新多边形内,找出距离最远的两个端点即可。

实现起来也不太难,由于数据规模不大(n最多100),直接用了O(n^3)的方法。具体做法是,首先算出各边沿斜率直角方向向内平移r后的新方程,然后求各直线间的交点。由于塌缩后,某些直线会完全位于有效区域之外,因此,对于这O(n^2)个交点,需要再用n条新直线方程判是否有效。最后,在剩余的交点中找出距离最远的两个即可。

[POJ 3317]

Stake Your Claim

博弈树搜索

题意:棋类游戏盘面大小最多8*8,两方轮流在空格落子,最后计算双方各自的最大4连通区域的大小来判胜负。空格最多10个。

题目数据范围很小,考虑到深度最多为10,一开始直接写了个O(n!)的算法,TLE。然后想明白可以用Alpha-Beta剪枝,还是TLE。最后发现空格最多10个,用位相量不过是4^10个状态,可以剔除重复状态的计算,然后就开始WA了。

持续的不解后……看了看讨论,才发现原来大家的思路都是一样的(卡了哪题都能看到ZaakDov…)。WA的原因正是Alpha-Beta剪枝导致子状态无法达到最优值,从而记录下了错误的状态值。正解是直接记忆化搜索即可。

[POJ 3133]

Manhattan Wiring

插头DP

题意:给出一个最多9*9的格盘描述,其中有两个2和两个3,求将两个2和3分别用不相交的线段相连所需的最短线长。线不能交叉且仅能经过空格。

明确是插头DP后,首先要考虑如何表示状态。这题特殊之处在于线段有两条,且需要和指定的端点相连。那么,最关键的是确定如何表达有效的终状态。

考虑以下解方案:用0表示空插头,2和3分别有相应插头。那么这里每个位置需要2bit,总共是2*10=20bit。再用最低的4个bit分别表示4个端点是否有连接,由此便能用一个int来表示轮廓线状态u。判断终状态时,直接用u==15即可。其正确性在于,题目所求的是最短线长,那么若4个端点已连,且当前没有空线头,则端点间必定已存在连线。同时也可能存在不与任何端点相连的线环,但线环仅仅是增加了总线长,不影响正确性。

实现方面,由于状态数为1<<24,若直接存下各个状态的最短线长,只能用char数组,由于题目数据范围不大,这是可行的。但一开始还是用了int数组和hash处理(u%10007)。按理说直接访问状态应该要比hash快,但事实上后者比前者要快两倍多(500ms VS 1600MS)。猜测是由于hash过后状态值较为接近,内存访问要更为集中。 虽然再加一些状态剪枝应该可以更快……但当前的代码长度竟已超过了5K,实在是不太明白那些2K左右跑出300ms的代码是怎么写的…… =(

[Ural 1519]

Formula 1

插头DP

题意:给出一个最多12*12的地形描述,问有多少方式在其上建一条F1赛道。一条F1赛道为仅横竖相连且覆盖所有空地的一个环。

参考了陈丹琦的《基于连通性状态压缩的动态规划问题》,插头+轮廓线扫描。虽然论文已经给出了基本思路,但仍存在一些特殊情况,如:

应转变为

我用了很长时间才发现这一点。

实现方面,一开始打算全部用位运算,但发现存储的时候状态数太多,无法直接开大数组存,于是又转成3进制。每次更新下一个格点的状态值时,交换内存指针,根据相应内存位置标记来判断是否置0,这样就避免了每次对大数组进行memset。另外,使用了栈来非重复地保存各状态(仅当计数值为0时入栈),避免了每次扫描整个计数数组。

在ural上用VS 2010只能跑出0.6S左右的时间。瓶颈应该是慢在位向量和3进制间转换上面,但也没有想到太好的优化了……

ps:小小感慨一下,连这种问题都可以DP,实在是不可思议。

[POJ 3131]

Cubic Eight-Puzzle

双向广搜

题意:有9个格子,其中8个格子有立方体。立方体相对的两面颜色相同,分别为红、白、蓝色。若某立方体相邻格为空,则该立方体可以向该方向滚动。给定初始状态,问最少滚动多少次(不大于30),可以使各盘格为指定的颜色。

类似这种搜索题,最关键的还是状态的表达。立方体的姿态可以由顶面和正面的颜色来确定,共有6种。有9个格子,考虑使用每个格子3bit,那么搜索空间的规模就是2^(3*9),大概是1亿。接下来,考虑如何判重。如果使用一个位来表示一个状态,总共需要的内存大小为2^24bytes,也就16MB,是可行的。直接开一个char c[1<<24],连hash都省了。由此,可以保证所有的操作全部为位运算。 接下来考虑搜索的形式,一般来说双向广搜,两边的深度限制相同时可以最大化降低复杂度,但该题反向搜索时起始状态较多(256个状态),层数不深时规模已经很大。因此,正向搜索深度设为20,反向为10。正向搜索完后如果找到解,则直接输出,否则才执行反向搜索。

虽然没有剪枝,但是内存全部静态分配并且大量使用位运算,程序非常的快(久违的刷到了第一)。

[POJ 3731]

Escape

构造 + 组合

题意:在n*m的网格表示的街道图内,移动时需要走格线,每个交点最多可以经过一次,在交点处可以选择直行或右转。问从起点(0,0)出发到终点(x,y)最多有多少种走法。

这题想了很久也没头绪,然后看了看discuss里有人提示用”转弯次数”+”组合数”分类讨论,然后细心推了一下:从起点到终点之间的路径,总是纵横交错的。并且其中纵向部分,南北两向总是交替出现的;横向部分同理。通过进一步分析可以发现,若确定了一条路径的转弯次数,那么其中横向和纵向的部分是相互独立的,也即该转弯次数对应的路径总数为横向部分的方案数与纵向部分方案数的乘积。横向、纵向的方案数是可以分别通过组合数来计算出来的。在某方向上,每次拐弯的位置必须是交错地位于终点坐标两侧的,那么总方案数即为从两侧选择拐弯位置的总可能数。例如,横向坐标范围0~4,终点在3时,那么横向拐2次弯的总方案数为C(1,1)*C(3,1),即先从4~4里面选一个位置拐一次,再从0~2里面选一个位置拐一次。不存在拐3次或以上弯的可能性。

到这里为止的推算还算是顺利的,倒是在计算C(n,k)上卡了很久的TLE。考虑了很久如何在模系统中实现除法,未果。于是开始分解质因数再累加,并想了各种优化……但都TLE了。最后才想起来可以利用C(n,k)=C(n-1,k)+C(n-1,k-1)来预处理出整个int C[2002][2002],于是就AC了。

[POJ 1093]

Formatting Text

DP

题意:输入行长度值n,以及一篇文章,要将文章重新排版,使得”坏值”和最小。若某行只含一个单词,则坏值为500。包含多个单词的行,单词间每个长度为k的间隔,其坏值为(k-1)^2。

这题之前做CLRS时见过类似的(第二版习题15-2),当时也是想了很久才推出公式:用dp[i][j]保存到第i行结束时,打印了前j个单词的最小坏值。那么dp[i][j]=min{dp[i-1][k-1]+bd[n-sum{w[l]|k<=l<=j}][j-k]},其中w[l]为第l个单词的长度,bd[p][q]为预处理计算出来的一行有p个空格和q个间隔时的坏值。状态转移时注意下标边界,并需要记录父状态。这里对于相同坏值的方案,要输出使各间隔长度值字典序最小的,那么每次dp使,总是使更多单词尽量在之前打印即可。 输出时,首先找到最小坏值对应的行数,根据父状态一层层回退,记录每行要打印多少个单词;然后逐个单词处理输出即可。

ps: 这题考察的算法貌似很有实用价值呢……不过现在的文本处理程序,恐怕是要比这复杂十倍以上的吧……

[POJ 1324]

Holedox Moving

搜索 + 滚动hash

给出最大20*20的格盘,和一条长为l的蛇,目的是使蛇头移动到(1,1)。格盘中存在k个格子是无法通过的石头,蛇也不能碰到自己的身体,包括最尾的一格。若能到(1,1),输出最短距离,否则输出-1。

考虑保存蛇的状态时,只保存其蛇头位置,以及指向之前蛇头的指针。由此就能搜索处理蛇的移动方案。考虑判重时,使用滚动hash,即到一个新状态时,将之前蛇头对应的hash值升位并减去之前蛇尾的部分,再加上新的蛇头值即可。

另外就是,可以考虑优先向(1,1)的方向移动,以及搜索之前先判断蛇头是否可达(1,1)。