早晨7:40的闹铃。到36楼下面见到了我的两个队友后,随便吃了点东西就出发了。
计算中心门前特别热闹,N多人围在一张大桌子前,好像是在签到。我挤进去找了半天发现没我的名字,名单上全是信科的人。我抬头问,中文的在哪儿呢。一个美女姐姐用手指了远处的一个几乎没人的地方说“中文的在那边”,并说了一句“哇,中文的呀,太牛B了”。我顺着她手指的方向望过去,另一张小桌子前面贴了“中文”二字,桌子后面没有人,估计是交给了旁边负责数院和元培的人,让他们顺便管一下。从我目前所了解的情况来看,那张桌子应该是特别为我准备的,它在历史上很可能是第一次出现。
第四题是做得最顺利的一道题。我把所有题粗略看了一遍后,首先决定就想这道题。题目描述巨简单,就是问你沿对角线把一个正n边形剖分成三角形和四边形有多少种方法。上图显示了n=5时所有的10种方法。熟悉组合数学的人都知道,三角形剖分方案对应的是Catalan数列,其递推公式的推导相当经典。设C(n)表示凸n+2边形的剖分方案数,枚举底边和哪一个点相连(下图左),容易看出C(n) = C(0)*C(n-1) + C(1)*C(n-2) + … + C(n-1)*C(0)。
现在,如果剖分中允许有四边形的出现,又该怎么办呢?看看数据规模n≤5000,估计应该是叫我们寻找类似的递推公式。容易想到,我们可以枚举底边与哪一个点相连构成三角形,统计出底边属于某个三角形的剖分方案T(n)=ΣC(i)C(j), i+j=n-1;再枚举底边和哪两个点相连构成四边形,统计底边在一个四边形上的剖分数Q(n)=ΣC(i)C(j)C(k), i+j+k=n-2。但是,枚举四边形需要O(n^2)的时间,这样的话整个程序就是O(n^3)的了,n=5000绝对超时。那怎么办呢?两分钟后,我想到了一个具有决定意义的点子:计算Q(n)可以直接利用以前算过的T(i)。枚举四边形的两个顶点时,固定四边形的左边那个顶点,你会惊奇地发现右半部分的所有情况加起来正好就是一个T(i) (上图右)。因此,ΣC(i)T(n-i-1)就是我们所需要的Q(n)。
一个有趣的细节是,这道题要求选手输出结果除以2^64的余数,不知道会不会有人想不到这个该怎么处理;事实上只需要直接用64位无符号类型来运算就可以了,超界了后计算机储存的本来就已经是2^64的余数了。
Hanoi塔那道题也是一个有趣的数学问题。题目大意是说,如果允许Hanoi塔上有相同大小的盘子,最优解又是多少。输入数据给出盘子大小的种数和每种盘子的个数,叫你输出最优解的步数。我的第一想法就是,大小相同的盘子始终要在一起,移动时就排着队啪啪啪地挨个移动,总是保证大家一起行动。其实说穿了就相当于把大小相同的盘子当成一个整体,只是移动的时候有一个权值。这样,只需要考虑经典Hanoi塔中每个盘子需要移动几次就行了。但很快我们发现,这连样例都过不了。为什么就只有两个一样大的盘子时,输出会是3呢?再仔细看了一遍题目,发现麻烦了:一样大的盘子也是可以区别的,目标状态的盘子顺序必须与初始时一模一样。但我们很快发现,加上这个条件后对我们的原始想法影响并不大。在经典Hanoi塔中,第i大的盘子需要移动2^(i-1)次,除了最大的盘子只需要移动一次以外,其它盘子都要移动偶数次。而对大小相同的几个盘子整体转移一次后,盘子的次序正好完全颠倒,因此移动偶数次后次序不变。于是,我们只需要集中考虑最大的那种盘子该怎么处理。队友leimiaos做出了一个大胆的猜想,事实证明他是对的:把最底下那个盘子加大一号(最大的那种盘子就变成第二大的了,并且少了一个),这样就巧妙避开了多个最大号盘子的问题;这样做的结果显然是合法解,并且应该是最优的。
第一题是所有题目中最科学的一道。给你一个有向图,给定它的起点和终点。每一条边都有且只有一段开放时间,其它时间里这条边是不允许通过的。给出每条边的开放起始时间、关闭时间和通过这条边所需要耗费的时间,问你从起点到终点最少需要多少时间。我和leimiaos同时想到,在这道题目中我们要尽可能早地到每一个点,可以用直接套用Dijkstra算法更新到每个点的最早时间。反正你早到了也不亏,如果接下来要走的边还没开放的话我等一下就是了。leimiaos很快写完了这道题,但连样例都过不了。仔细研究了一下样例,我们发现问题严重了:我们要求的不是到终点的最早时间,而是从起点到终点全程所需的最少时间。换句话说,我可以先在起点处等着不走,看着时机成熟后再出发,虽然到终点时间更晚,但路上花的时间更少。怎么办呢?枚举出发时间是一个好办法,但时间的范围很大,肯定会超时;二分出发时间?显然不行,这个问题不具有单调性(考虑从起点直接连到终点的多重边,且所有边的开放时间都不相交)。十分钟后,我一拍大腿说,至少存在一个最优解,使得整个路程在某条边上正好卡着时间进去或者卡着时间出来。如果整个路程中所有边上的实际通过时间都是“松”的,就把整个行程时间安排挪动一下,直到某条边上的通行时间正好碰到开放时段的端点。这样的话,我们只需要枚举整个行程时间卡在哪条边上,然后用刚才的算法顺着推出从这儿走完剩下的路到终点的最早时间,并且反着推出从这儿往回走能够得到的最晚出发时间。这道题太精彩了!!可惜后来时间不够,算法的代码没有完成,算法的正确性和效率也不得而知了。
第二题是一道很难的数学题,很少有人做出来。因此我非常得意:) 说一个n阶的满Steiner树是指一个有2n-2个节点的无根树,其中n个叶子节点从1到n标号,另外n-2个节点(叫做Steiner顶点)连通了那n个点,并且每个Steiner节点的度都为3。你可以在这里看到n=3时的惟一一个满Steiner树,以及n=4时的所有三个满Steiner树。输入一个n(3≤n≤10^7),输出n阶的满Steiner树有多少个。要求用8.687E36这样的格式输出。
随便把一个叶子节点砍掉,剩下的就是一棵正宗的二叉树,我们的问题实际上就变成了“有n-1个叶子节点的二叉树有多少个”,其中叶子节点带标号,并且二叉树不分左右。依据这个思路得到的递推公式就是( ΣC(n,i)F(i)F(n-i) )/2,那个C是组合数的意思,F(n)表示n阶满Steiner树的个数。但是n可能达到10^7,绝对不可能直接用我这个递推公式。我开始计算n=3, 4, 5, 6的情况,想看看有什么数学规律没。算出来的数是1, 3, 15, 105。然后牛B的事情就发生了,我眼睛突然一亮,发现它们间的比正好是3, 5, 7,也就是说F(n)=(2n-5)!!!(前两个叹号是双阶乘的意思,末一个感叹号是感叹号)。我立即写下一个程序,按这个公式计算F(30),发现它果然得出8.687E36,和样例一模一样!
真正戏剧性的事情发生了:暴力计算双阶乘超时了。怎么办?注意到,(2n-1)!!=(2n)!/(2^n*n!)。leimiaos突然想到,在这个精度要求下完全可以用Stirling近似公式。我们可以让n到了一定大时利用Stirling近似公式直接输出结果。n的阶乘约等于根号下(2πn)乘以(n/e)^n,在比赛中我们发现这个公式的精确程度令人瞠目结舌。非常神奇地,π和e居然出现在了阶乘的近似公式中!
发现Problem I是一道水题已经是很晚的事了,最后仍然没来得及把它调试完,太可惜了。前40名队伍中就只有我们没做这道题。Problem G据说也是水题,很多队伍都做了。Problem E也是一道相当科学的题目,我发现了一个nlogn的算法,但没时间写代码了。还有一道题目就是Zen Puzzle Garden游戏求解,一看就是BT的搜索,没一个参赛队伍做了那道题。Zen Puzzle Garden真的是个电脑游戏,就和尚扫地那个,网上都有下载的,还不错。另外两道题分别是一道简单的计算几何和一道简单的图论题。leimiaos顺利完成了那道图论题,计算几何那道题我们都不敢写。所有的题目都可以在这里看到。
最后的结果,我们只AC了10道题中的4道。ACM果然是拼时间的,好多题目我们都会,就是没时间写了。我N多年没写过代码了,昨天绝大多数代码都是leimiaos写的。也好嘛,这次就是抱着玩玩的心态来的,也长了一些经验。明年赛前我可能真的会认真准备一下。还有三年的机会呢,慢慢来吧。
整场比赛的第一名AC了8道题,其中5道都是一次通过。你们猜这个队伍是哪三个人?告诉你,牛大B了,真的牛大B了。三个人分别是唐文斌、郭华阳、王栋。这次比赛是允许外校队伍报名参加的。
最后,非常感谢网友dahe_1984请我吃饭喝酒!昨晚的聊天非常愉快,喝得也很尽兴。
沙发…
清华的三位果然是牛B大了
。。N和π长的太像了,我还以为你打错了。
还有斯特林我看成了string。
ACM必须要求全对实在是闹心。5月31日偶参加JLACM,祝自己但愿能顺利。
呵呵,才睡醒起来。
Problem E是拓扑排序么?
记得当年我也参加过校内的ACM赛,不过是个人赛,8道AC了4道
当然难度不能和LZ那里比(YoY)/
清华的三位牛。。。
Je te reconnais bien là Gr.eainnca.. Moi, j’aime bien ta pizza de la mauvaise humeur! Ca redonne le sourire, dans un jardin avec un petit verre à la main (le petit verre ça redonne toujours le sourire…)!
大牛的数学还是难以匹敌的啊
dahe_1984就是那位Matrix78吧..
再次路过 -_-||
请问你知道本次比赛的board在哪能看到么?
中文…orz
OICQ…
OICQ:顾森,雷淼,余昊男 4 北大中文
牛人 真牛
#include
int main(){
long long x[5005]={0,0,1,1,3};
short i,j,n;
scanf(“%d”,&n);
for(i=5;i<=n;i++){
x[i]=x[i-2]*3;
for(j=2;j<=i;j++)
x[i]+=x[j]*x[1+i-j];
}
printf(“%lld”,x[n]);
}
过路人
Hei, aloin kutomaan sukkaa tällä kuviolla, ja laitoin 12 silmukkaa yhdelle puikolle, eli yhteensä 48 silmukkaa, mutta sukat eivät kyllä mahdu edes katnspääatä läpi. Kuinkahan paljon silmukoita pitäisi yhteensä olla, että sukat mahtuisivat noin 38 kokoiseen jalkaan? Täytyykö muuten sitten kaventaa kantaapäätä ja jalkaterä osaa varten?