“金山WPS杯”程序设计大赛感想

void 华工感受()
{
  本来这次比赛就要自己带午饭,结果今天早上起晚了,结果慌慌张张连早餐都没吃,只好带了几块饼干就去了华工。好在饼干还算比较饱肚子,比赛时没有饿到。
  由于时间紧迫,我没有认真地逛一圈华工。总体来讲,华工给我一种很舒服的感觉。这种“舒服”来源于建筑的结构、颜色以及周边的植被分布。看来华工的设计师比较对我的胃口
  不过华工的道路比中大要复杂很多,我回去的时候就迷路了。最后只能骑着小黄车开着高德地图的导航回来。
  华工的电脑没有Linux,Windows系统只有Visual Studio 2010,我只能用那个已经一年半没用的Dev-Cpp。我觉得IDE这东西,要么就不用,要用就用好的。像Dev-Cpp这种IDE就完全可以用editor代替。比较幸运的是,Dev-Cpp的调试功能没有出问题,没有自招那次坑爹。
  总体来讲,编程环境还算满意,没有太多的意外发生。
}
int main()
{
  今天刚拿到题还没把B题看完时,我的队友就把A题做出来了,他告诉我A是水题,于是我这题看都没看,跳过。
  B题的题意是:给你三个硬度相同的水晶球,并且有一栋高n层的楼。你可以选择从某一层扔下这个水晶球。这三个水晶球有一个相同的临界楼层。如果你在超过临界楼层的地方扔下水晶球,水晶球就会爆炸,反之则会毫发无损。现在你要测出这个临界楼层,问你在最坏情况下最少要扔多少次。 n <= 10^14.
  一开始我以为是先用前两个水晶球二分确定一个1/4区间,然后再用第三个水晶球从最低一层开始一层一层丢。当时我还说了一句“B也是水题”,结果光荣的WA了。。。
  这种做法的问题在于:当你从二分的中点处丢下水晶球时,如果一个水晶球没摔坏,它是可以继续扔的,但是如果摔坏了,就得用下一个水晶球。所以,这样直接二分是不对的。
  接着我就开始思考其他的策略,但是想来想去还是不知道有什么更优的做法。于是我就开始做D题
  {
    D题的题意是:给定n,m(n<=200,m<=s(n)),其中s(n)为小于等于n的素数的个数。对于一个1~n的排列an,统计满足如下条件的排列个数:对于1~n的素数p,满足ap=p的个数恰有m个。
    这道题是一个比较明显(至少我是这么觉得的)的容斥原理。首先,我们用筛法求出s(n)的值。然后,我们首先从s(n)个素数中选出m个素数,然后对于剩下n-m个数,我们要保证其余的s(n)-m个素数是错排的。
    设X(n, m)表示n个数的排列中,m个数处于错排状态(这m个数不是随意的,而是指定的)的排列个数。则C(s(n),m)*X(n-m,s(n)-m)即为答案。
    那X(n, m)怎么计算呢?当然是用容斥原理。首先,如果不考虑这m个错排,则有n!种方案。但是现在有m个位置需要错排,所以要减去这m个位置没有错排的情况,即C(m,1)*(n-1)!,但是这样同时两个位置都没有错排的情况会多减,所以要加回C(m,2)*(n-2)!,以此类推。因而得到
    X(n, m) = n!-C(m,1)*(n-1)!+C(m,2)*(n-2)!-...+(-1)^m*C(m, m)*(n-m)!
    这道题还有个坑。题目要输出答案对一个大整数取模后的结果。但是这个X(n, m)有加有减,所以最后可能小于0.因此在返回X(n, m)的时候要return (ans + bigint) % bigint;
  }
  D题做完后,我萌生了找B题规律的想法。首先我写了一个dp:用dp[i,j]表示有i层楼,j个水晶球时所需的要扔的次数。然后,枚举扔球的楼层k,可以写出这样一个方程:
  dp[i,j] = min{max(dp[k-1, j-1], dp[i-k,j]) | 1<=k<=i} + 1;
  dp[0,j] = 0;
  dp[i,1] = i;
  然后,我先对较小的i输出了dp[i,3]的值,看了一下没发现什么规律。
  于是我又输出了dp[i,3]取最小值时对应的k的值,即第一次扔球的楼层。
  我发现随着i的增长,这个楼层会先从1开始逐渐递增到一个特定的数值,然后回到1再增长到另一个数值,又再回到1再增长。。。
  而且,每次回到1时,dp[i,3]的数值都会增加1,而当这个楼层增长时,dp[i,3]不变。
  于是,我把每次跳回1之前的那个楼层记录下来,排列成数列{an}。这时我队友提醒我an是一个二阶等差数列,即它相邻两项的差是等差数列。通过对等差数列数列求和得到了an的通项。
  然后,由于只有每次跳回1的时候,dp[i,3]才会增加1。因此,我们实际上是要找到一个正整数m,满足
  a1+a2+...+am < n <= a1+a2+...+am+a(m+1).
  我们只要对an再求一次和然后二分就能找到这个m。进而求出答案。
  被我称之为水题的B题终于过了之后,我队友看到F题做的人比较多,于是我开始想F题。
  F题的题意是:找出1~n中所有满足没有两个相同的质因子的整数的个数,即分解质因数后任何一个质因子的指数都为1。n<=10^14
  受到D题影响,我又想用容斥原理了。首先,用筛法求出小于等于sqrt(n)的所有素数,然后记它们的平方分别为a1,a2,...,am.
  这样,答案就是sum{n/ai} - sum{n/(aiaj)|i≠j} + sum{n/(aiajak)|i,j,k两两不等}-...
  为了能更好的实现这个算法,我写了一个深度优先搜索。由于答案的每一项都是形如n/(aiajakax...),这个搜索分别枚举ai是否出现在分母中,然后根据分母个数的奇偶性决定符号。
  然后,我加了一个剪枝:如果当前的分母已经大于n,则退出,因为此时分数一定为0.
  你一定觉得这么暴力的做法对于10^14这么大的数据肯定会超时。我刚写出来也是这么认为的,但奇迹的是,它过了。
  它居然过了。。。
  而且,这道题和D题都是用了容斥原理和筛法。怕是这两题的出题者背后有着肮脏的。。。。
  做完F题之后,我们发现相当多人都做出了L题,于是我开始做L题。L题题意很复杂,简单来讲就是在先进先出的队列的基础上支持查询操作。只要离散化后开一个标记数组就好了。
  这道题有毒的地方在于,这道题的数据范围是<=10^5,但是我把数组开成100100时Runtime error了。然后我检查了很久都没发现问题。最后我把数组大小末尾加了一个0,然后就AC了。
  就AC了。。。。真有毒。
  出现这种情况,我能做的当然是选择原谅数据啦!
  L题过了以后,我开始想E题,但是根本没有头绪。这时我队友建议我看G题。G题的题意是:有两个人爬一个N层的塔。在爬的过程中,位于较下的人会有P的概率向上爬一层,1-P的概率向下爬一层(如果他本身就在第一层,则不动),整个过程耗费1个时间(即使不动也会耗费)。上面那个人不动。如果两个人在同一层,则规定某一个人进行上述行动,另一个人不懂。问这两个人都爬到第N层所需的时间的期望。N <= 40
  我队友提示我两个人是对称的,所以只要算出一个人爬到塔顶的期望再乘以2就可以了。我一想挺有道理的。
  那怎么求一个人的期望时间呢?我们设xi表示从第i层爬到塔顶所用的时间的期望。
  则根据题意,x1 = P*x2 + (1-P)x1 + 1.
  xn = P*x(n+1) + (1-P)x(n-1) + 1.
  且xN = 0.
  似乎不能直接递推计算。但是,我们可以稍微变下形:
  x2 = x1-1/P.
  x(n+1) = 1/P(xn+(P-1)x(n-1)-1)
  接着,我们把x1当成未知数,利用上式递推,将xn表示为a+bx1的形式,然后利用xN=0解出x1即可。
  这样做时间复杂度是O(N)的。题目给的数据范围似乎有点小。我估计题目是用了高斯消元去求x1。
  L题做完后,时间也不多了。我就进入了划水阶段。我基本就没再做出贡献,只能默默的围观队友。
  最后,我们一共做出了6题,排名第8,还是比较满意的。
  return 0;
}

posted @ 2017-04-23 22:19  Jason5Lee  阅读(261)  评论(0)    收藏  举报