Problem A
本题的大意是,给出一些机票,每个机票都是一条路线,比如城市A->城市B->城市K->…->城市N,并且每张机票有一个价格。我们可以只用一张机票的一部分,比如城市A->城市B->城市K,然后就丢弃这张机票。但有两个条件,第一,必须在机票的起始城市才能使用机票,也就是说,我们不能用上面的机票从城市B到城市K;第二,如果使用了一张机票的部分,以后就不能使用剩下的部分。
现在给出一条路线,我们要按顺序访问一系列的城市。给出所有可以购买的机票,每种机票可以买无限张,问怎样可以用最少的花费完成整个旅途。
本题的数据规模颇小,机票最多20种,而每个机票最多经过10个城市。由于机票可以重复购买,城市必须按顺序经过,很容易想到要用动态规划。但从比赛过程中可以发现,无数的队伍被这题卡住了,而且很少的队伍能够一次通过。问题就在于,并不是只能访问指定路径上的城市,而是可以访问一些辅助的城市来减小花费。所以,我们要用一个二元组(i,j)来表示一个状态。其中i表示指定路径上已经按顺序访问了的城市数量,j表示当前所在城市。通过机票的信息,不难得到状态之间的一个有向图,而我们要求的其实就是一个最短路径。注意到这个图是有圈的,所以我们不能直接用动态规划,而是需要用最短路算法。本题初看觉得规模甚小,此时则可以发现最多能有20*10=200个城市,共可以有10*200=2000个状态,还是颇有规模的。
总结:想清楚后此题并不复杂。但比赛时必须保持头脑清醒,分析清楚题意,才可能顺利解决此题。比赛中虽然很多队伍做出此题,但很少有队伍一次做对,更有一些队伍一直困在这题,可见比赛中队伍普遍紧张,没能仔细的去考虑。
Problem B
典型的最小费用最大流,不多说了。二分图匹配也可以,但得到的图规模太大。
Problem C
物理题,恐怕还需要不少计算几何。比赛的时候没人做,我也没仔细看。
Problem D
题目大意:形如a…ab…b的数叫做bipartite number,比如1222,333999999,50,8888,1等等都是。给出一个数字x(x<=99999),求出x的最小的倍数n=kx(k>1),使得n是bipartite number。
这是一道比较tricky的题目。比较容易想到的方法是,长度为p的bipartite number很有限,最多有p*9*10个,可以一直枚举上去,直到得到x的倍数。但编程试验一下就会发现,有时候直到上千位也没出现x的倍数。主要问题在于,如果x的末尾是0,那么n的末尾也必须是0。比如x=99990,那么n会形如aa…a0,而且aa…a是9999的倍数,这时候,只有一个自由度,所以我们可以期望得到的答案的长度是O(x)的。此时如果我们还用枚举所有bipartite number的方法,最多可能枚举90*(9999^2)/2个数,时间上不能接受。
解决这个问题的办法是,可以看到此时末尾已经确定,所以我们只需要枚举前面的部分,这个是可以在O(x)的时间内完成的,可以接受。但出现了另一个问题,末尾究竟有几个0?有人可能认为,x的末尾有几个0,n的末尾就有同样数量的0。这是错误的。不妨考虑x=250,任何形如a…a0 (a>0)的数都不是250的倍数。因为a只可能等于5,而55不是25的倍数。所以,n的末尾可能还需要更多的0。经过分析不难发现,如果x末尾的0都去掉后还是16或者25的倍数,则我们需要更多的0。而当x是2,4,8的倍数,或者5的倍数的时候,额外的0并不是一定需要的,但可能使得解更小。此时我们需要枚举0的个数,不过最多枚举3种情况,时间上完全可以接受。
当x不是10的倍数的时候,bipartite number对x的余数可以看成是均匀分布的,此时直接枚举我们可以期望在O(x)的时间内得到解。我编程试验过,对于x不是10的倍数的情况直接枚举几乎不需要时间。但是,为了保证得到的是最小解,必须注意枚举的顺序。
总结:一道很考验选手综合素质的题目。严谨的数学上的分析,对各种细节的处理,还有编程实现上的技巧都是非常必要的。上面的分析虽然很长,一旦想清楚了程序可以写得很短。
思考题:为什么这样的bipartite number一定存在?
Problem E
题目大意:给出一个01串和一个压缩方法:把所有连续的1替换成其长度的二进制表示,只要这样的替换能够缩短字符串。比如1111101110010就会变成1010110010。现在给出压缩后的串(长度<=40),和压缩前的串长(<=16000)还有1的个数,问解码方法是否唯一。
可以动态规划。虽然原串中1的数量可能很多,但0的数量最多40个。所以状态最多16000*40*40个,而其中的大部分状态都不会出现。要注意的一点是,得到的原串不但要保证0和1的数量正确,也要保证能压缩的片段都被压缩了。
这题的规模不大,也可以考虑搜索。这样需要一些剪枝。这题各种剪枝的方法很多,但要做到在时限内得出正确结果也不容易。
Problem F
我没理解题目,看不懂齿轮的契合方式。感觉上是搜索或者解方程之类的题目。比赛的时候没什么人做,也没人做对。
Problem G
题目大意:Jack带了一帮人去朝圣。Jack是管钱的,他会遇到四种操作:pay,collect,in,out。pay就是付一定数量的钱,collect是从让每人交一定数量的钱,in是新加入若干人,每个人要交当前总钱数/当前总人数这么多钱。out是离开若干人,每个人拿走当前总钱数/当前总人数这么多钱。注意,in和out的时候,可能出现每个人平均的钱是分数的情况。题目中说,假设Jack很幸运,每次都正好整除了,问他开始可能带了多少人。
这题看似不好下手,不过我们可以认真的分析。不难发现会出问题的只是in和out这两种操作。假设第一次in或out(不妨假设是in p)的时候,我们有n个人,平均每个人的钱是k,那么in p之后,我们有n+p个人,每个人的钱还是k。collect操作不影响当前的整除性。不妨假设接下来两个操作分别是pay q和in t,那么pay q后有n+p个人,每个人的钱是k-q/(n+p)。这必须是整数,因为下一个操作是in。所以n+p必须是q的约数,从而n只可能有有限个。
从上面的分析不难得出结论,如果我们把in和out叫做critical operation,那么问题有有限个解当且仅当至少存在一个pay operation夹在两个critical operation之间。
我们可以不断重复上面的步骤,把所有连续的critical operation之间的pay都合并起来(注意,collect操作对整除性没有任何影响),然后得出一系列整除式,每个都是形如(n+a)|b。不需要去求解整个系统,n一定是b-a的约数,所以对一个式子求出所有约数,然后代入其它式子检验即可。
这个算法似乎太简单了一点,比赛中不应该只有这么少的队伍做对。或许有些地方我没考虑到,大家不妨指正。
Problem H
似乎是一道挺麻烦的折纸题,比赛的时候没人做。
Problem I
送分题。求两两间最短路中最长的一个。简单的floyd算法,放在topcoder绝对5分钟内一堆人做出来了。
Problem J
题目大意:给出一个有向图和两个点s1,s2,求一个最小的顶点的子集,使得在这个子集induce的子图中这两个点相互可达。
其实可以看成是我们要选择两条路径,一条从s1到s2,另一条从s2到s1,使得路径上出现的顶点数量最少。首先,s1和s2必须在子集中。让S(v1,v2)表示要使得v1,v2强连通必须选择的除v1,v2外的顶点个数,d(v1,v2)表示丛v1到v2的最短路,那么显然S(v1,v2)<=d(v1,v2)+d(v2,v1)-2,因为如果两条最短路径没有重合的顶点,那么出现的顶点总数就是d(v1,v2)+d(v2,v1)-2。考虑有重合顶点的情况,不妨假设中间有一个点t,那么要让v1,v2强连通,我们可以让v1,t强连通并且t,v2强连通,所以S(v1,v2)<=S(v1,t)+S(v2,t)+1。如果让T=S(v1,v2)+1,那么T(v1,v2)<=T(v1,t)+T(v2,t)。这看起来难道不熟悉么?这个就是说,我们可以把T(v1,v2)看成v1,v2间的一种最短路径。
这样算法就变得清晰了,先用floyd算法求出d(i,j),然后让T(i,j)=d(i,j)+d(j,i)-1,再用一次floyd算法更新T(i,j)的值,最后的答案就是T(s1,s2)+1。整个算法的主体部分不超过10行代码。
总结:
A,B,D,E,G,I都是属于“较好做”的题目,J属于算法性强的一道图论题,剩下三题不太好说难度,但至少不常规。Final中大家还是很谨慎的,不会轻易去做一道不常规的题,也不会轻易去做别人没做对过的题。虽然最后冠军是6题,但我觉得如果一些队伍其实有做出7题的实力。Final这样的比赛,还是很看发挥。
A,B,D,E,G,I这6题中,I属于必对题,B属于基本题,A有一点trick,但也应该做对;D,E略为复杂,各有一些陷阱,但也属于该做对的题目。G只要仔细分析其实很简单。J题如果想出了算法,写程序只是5分钟的事。当然了,比赛中要做到这些,远比一个没有压力的旁观者分析的要难得多。
剩下的C,F,H三题基本没人做,我现在没有太多的时间所以也没细看,如果有人有想法请说出来吧。