MAIGO的同济题解1

Welcome to
Tongji Online Judge Solutions by Maigo Akisame Volume 1

我在TJU的所有AC程序及本题解均可在下载。

purety.jp/akisame/oi/TJU.rar下载。
题号 状态 题号 状态 题号 状态 题号 状态 题号 状态 题号 状态 题号 状态 题号 状态 题号 状态 题号 状态
1000 ac 1001 ac 1002 ac 1003 ac 1004 ac 1005 ac 1006 ac 1007 ac 1008 ac 1009 ac
1010 ac 1011 ac 1012 ac 1013 ac 1014 ac 1015 ac 1016 ac 1017 ac 1018 ac 1019 ac
1020 ac 1021 ac 1022 ac 1023 ac 1024 ac 1025 ac 1026 ac 1027 ac 1028 ac 1029 ac
1030 ac 1031 ac 1032 ac 1033 ac 1034 ac 1035 ac 1036 ac 1037 ac 1038 ac 1039 ac
1040 ac 1041 ac 1042 ac 1043 ac 1044 ac 1045 ac 1046 ac 1047 ac 1048 ac 1049 ac
1050 ac 1051 ac 1052 ac 1053 ac 1054 ac 1055 ac 1056 ac 1057 ac 1058 ac 1059 ac
1060 ac 1061 ac 1062 ac 1063 ac 1064 ac 1065 ac 1066 ac 1067 ac 1068 ac 1069 ac
1070 ac 1071 ac 1072 ac 1073 1074 ac 1075 ac 1076 ac 1077 ac 1078 ac 1079 ac
1080 ac 1081 ac 1082 ac 1083   1084 ac 1085 ac 1086 ac 1087 ac 1088 ac 1089 ac
1090 ac 1091 ac 1092 ac 1093 ac 1094 ac 1095 ac 1096 ac 1097 ac 1098 ac 1099 ac

Prob 1002: 全排序问题 回页首
  放心大胆地做吧,不会TLE:)

Prob 1004: 防御导弹 回页首
  第一问即经典的最长不上升子序列问题,可以用一般的DP算法,也可以用高效算法,但这个题的数据规模好像不需要。
  高效算法是这样的:用a[x]表示原序列中第x个元素,b[x]表示长度为x的不上升子序列的最后一个元素的最大值,b数组初值为0。容易看出,这个数组是递减的(当然可能有相邻两个元素相等)。当处理a[x]时,用二分法查找它可以连接到长度最大为多少的不上升子序列后(即与部分b[x]比较)。假设可以连到长度最大为y的不上升子序列后,则b[y+1]:=max{b[y+1],a[x]}。最后,b数组被赋值的元素最大下标就是第一问的答案。由于利用了二分查找,这种算法的复杂度为O(nlogn),优于一般的O(n 2)。
  第二问用贪心法即可。每颗导弹来袭时,使用能拦截这颗导弹的防御系统中上一次拦截导弹高度最低的那一套来拦截。若不存在符合这一条件的系统,则使用一套新系统。
   注意:第二问不可以用每次删除最长不上升序列的方法做。比如,当来袭的导弹共有4颗,高度依次为2 4 1 3的时候,如果不巧找到的最长不上升序列为4 1,那么剩下的2和3就只能另外启用两套系统了。这显然不是最优解。

Prob 1005: 母牛生小牛 回页首
  用a[i]表示第i年牛的总数,则a[i]=a[i-1]+a[i-3],即3年前所有的牛所生的小牛加上上一年所有的牛。

Prob 1006: 敲七 回页首
  挨个检查即可。

Prob 1009: 蛇行矩阵 回页首
  把三角形的下角拖到右面去,便可以编出连数组都不用的程序了:)

Prob 1010: 数素数 回页首
  用筛法求素数即可。关键是怎么把求得的素数都保存下来。可以用1个字节保存8个二进制位,即8个数是否为素数。再用count[i]表示i*8以内素数的个数(前缀表示法)。求m到n之间素数个数时,除了用两个count值相减外(前缀表示法的常用技巧),还要注意处理一下m和n附近的素数。

Prob 1011: 阶乘末尾非零数求和 回页首
  在乘的过程中,我们只需保留最后一位非零数。这位数在n>1时为偶数。当要乘的数中含有因数5时,我们可以把所有的因数5都当作8来乘,因为:
...2*5=...10(舍)或...60,最后一位非零数为6。而恰好2*8=16,末位为6。
...4*5=...70(舍)或...20,最后一位非零数为2。而恰好4*8=32,末位为2。
...6*5=...30(舍)或...80,最后一位非零数为8。而恰好6*8=48,末位为8。
...8*5=...90(舍)或...40,最后一位非零数为4。而恰好8*8=64,末位为4。

Prob 1012: 约瑟夫问题 回页首
  用数组模拟链表即可。

Prob 1013: 高精度整数去位取最小问题 回页首
  每一个保留的数字都从可取的范围中取最小的一位即可。用a[i]表示第i个保留的数字在原数中的位置,设a[0]=0,则a[i]的范围为a[i-1]+1~n-m+i。

Prob 1014: 阶乘结果末尾有多少零? 回页首
  借用化学术语说,“一个2跟一个5反应生成一个0,因为2过量,所以照5算”。n!末尾零的个数就是1~n这n个数中因数5的个数,即n div 5+n div 5 2+n div 5 3...直到某项减少至0为止。

Prob 1016: 请求N!左边第二位的数字 回页首
  用实数乘,超过100就除以10,最后取个位即可。

Prob 1017: 石子归并 回页首
  开一个最小下标为0,最大下标为最大总质量的一半(maxw)的布尔数组b,初始时仅b[0]为true。依次读入每一个石子的质量。设当前石子的质量为m,则对于原来的任一个值为true的b[i](0<=i<=maxw-m),令b[i+m]为true。处理完每个石子后,设值为true的b值中最大下标为x,则x就是把石子按质量尽可能平均地分为两堆后较轻的那一堆的质量。

Prob 1018: 编制一个乘法运算的程序 回页首
  垃圾题。不要按照一般的乘法竖式输出,而要观察样例程序的输出,然后照葫芦画瓢……SPOJ上有一道Simple Arithmetics,比这个题好得多,推荐做一下。

Prob 1021: 麻将游戏 回页首
  说是麻将游戏,其实就是连连看嘛。好像有不少人看不懂题意,可以Google一个连连看玩一下。
  这个题的解法就是简单BFS。从起点出发,每次扩展就是沿上下左右四个方向闯下去,直到碰到一张牌或边界为止。这里的“边界”应是原棋盘再往外的一圈,因为连线可以超出棋盘。如果碰到的牌正好是终点,那么BFS成功。
  另外还要注意,输出的应是路径上线段的条数,而不是拐的弯数。

Prob 1022: 数制转换 回页首
  初学者的基础题,我就不讲算法了。要说的是题目叙述的第一行不应是“A$='mp'”,而应是“A$='mp'”,被当成HTML标签吃掉了。看一下样例你就不会迷茫了:)

Prob 1024: 特殊三角形 回页首
  这个题是TJU上一道极垃圾的题。虽然是Special Judge,但实际上能AC的只有按升序排列在最前面的那一个三角形。这样看来,连样例输出都是错的:(

Prob 1025: N*N的棋盘 回页首
  深搜。为了加快速度,可以不用布尔数组记录一个数是否用过,而用数组模拟的链表。

Prob 1026: 整除65的多项式 回页首
  注意到k,a,x的范围其实都只是0~64,于是枚举即可。

Prob 1027: 洗牌问题 回页首
  • Maigo的原始算法:
      我们把每个数逛来逛去最后又回家的过程叫做一个循环,循环中经过的位置个数叫做循环的长度。如N=4时,有两个循环:1-2-4-8-7-5-1,长度为6;3-6-3,长度为2。答案就是所有循环长度的最小公倍数。显然算法时空复杂度均为O(n)(因为需要记录一个数是否已被某个循环经过)。
  • Wasltone的高效算法:
      1所在的循环长度就是答案。时间复杂度小于O(n),空间复杂度为O(1),编程复杂度也远低于原始算法。这个算法是建立在如下结论之上的:“1所在的循环长度是其它任一循环长度的倍数”,或者表述为“1回家时,其它任一数字也一定回了家”。Wasltone本人没有给出这个结论的证明。
  • Ahyangyi给出的证明:
      题目中的移动规则,其实就是每次把在第x个位置的数移动到位置x*2 mod (2*n+1)。这个式子是十分巧妙的,请用心领悟。由这个式子可以得出任一数字x在p步之后的位置:x*2p mod (2*n+1)。假设1经过p步回了家,那么可得1*2p mod (2*n+1)=1。由此可得对任一数字x,均有x*2p mod (2*n+1)=x,即1回家时任一数字都回了家。

Prob 1029: 埃及分数 回页首
  本题若用BFS,则每个结点可扩展的子结点数会巨大,空间复杂度无法承受。因此采用DFS-ID,即先假设解的深度(单位分数的个数)为1,进行DFS,若不成功,再假设解的深度为2,DFS……直到找到解为止。
  DFS-ID解决了空间问题,但还有一个问题就是每个结点扩展子结点的范围,因为单位分数的分母没有限制,必须人为找一个限制条件。
  • 对于非叶子结点:当前待确定分母的最小值就是max{上一个分母加1,当前剩余分数的倒数的整数部分加1}。第2项之所以加1,是因为当前分母必须小于以后的分母。它的最大值则是min{当前剩余分数除以尚未确定的分数个数的商的倒数的整数部分,当前最优解的最后一个分母减1}。前一项可以这样理解:如果当前待确定分母大于这一项,那么以后的分母都会大于这一项,当前待确定的及以后的分数加起来肯定小于当前剩余分数。后一项则是显然的:如果当前分母都大于等于当前最优解的最大分母了,那么当前结点一定不会扩展出更优解。
  • 对于叶子结点:这里就无需讨论什么范围了。若当前剩余分数的分子恰好为1,且分母大于上一个分数的分母,且分母小于最优解的最大分母,则更新解,否则退出。
  上述过程中,为计算准确及在叶子结点处获得剩余分数的分母方便,剩余分数不要采用实数类型计算,而是设两个整型变量分别表示分子和分母。

  注:本题的题目描述不甚清楚。在多解的情况下,题目只说要分数个数最少,分数个数相同的情况下最大的分母最小。但是,若最大的分母还相同怎么办呢?比如8/27,是分解成1/4+1/36+1/54,还是分解成1/6+1/9+1/54?对此,我的程序输出的是找到的第一个解4 36 54,亦即认为在最大的分母还相同的情况下,取字典顺序最小的一个。这可能不是出题者的原意,但用这种标准,我AC了。


Prob 1030: 字符串的序号 回页首
  假设我们要求kensta的序号,那么我们只要求出以a开头的、以e开头的、以ka开头的……字符串各有多少,累加就行了。剩下的任务就是已知一些字符,求它们可以组成多少个不同的字符串。这是一个重排列问题。假设共有n种字符,其中字符c i有a i个,那么求这些字符可以组成的不同字符串数的公式为

Prob 1031: 猫和老鼠 回页首
  宽搜即可,因为总状态数只有(10*10*4) 2=160000。

Prob 1032: 等式问题 回页首
  先用预处理算出符号的所有填法对应的结果,然后问哪个输出哪个就行。

Prob 1033: 线型网络 回页首
  此题的确定算法,不是TLE就是MLE。这时,随机算法就大显神通了。
  首先随机生成一条路径,然后对其进行优化。用dist[a,b]表示路径上第a个点与第b个点之间的距离。假设存在某一对a,b使得dist[a-1,a]+dist[b,b+1]>dist[a-1,b]+dist[a,b+1](规定dist[0,*]=dist[*,N+1]=0),那么把第a个点至第b个点这一段路反向,得到的新路径会更短。不断把某一段路反向直至无法再优化为止。
  这样做并不能保证得到的解最优,因为有时“退一步海阔天空”,而这种本质是贪心的随机算法却“退”不下这一步。然而,这种情况毕竟是少见的。因为N的范围并不大,所以把上一段所述的过程重复一定次数(我的程序中为99次),就基本可以保证得到最优解了。

Prob 1034: 四塔问题 回页首
  我们把利用三个、四个柱子移动i个盘子所需的移动次数分别记为f[i]、g[i]。为了推出g[i]的表达式,把用四个柱子移动i个盘子的过程分为三步:
  1. 把上面j个盘子移到某一工作柱上。这一步可以用四个柱子,所需移动次数为g[j]。
  2. 把下面i-j个盘子移到目的柱上。因为上一步占用的工作柱在此步中不能使用,因此此步所需移动次数为f[i-j]。
  3. 再把工作柱上的j个盘子移到目的柱上,所需移动次数为g[j]。
因此得到递推式:
g[1]=1
g[i]=min{g[j]*2+f[i-j]}(1<=j1)
  然而仅仅利用这个式子,算法复杂度是O(n 2)的,对于n<=50000的数据范围,显然无法承受。因此,我们列出i值较小时的f[i]和g[i],以期发现某种规律。
i 1 2 3 4 5 6 7 8 9 10 11
f[i] 1 3 7 15 31 63 127 255 511 1023 2047
g[i] 1 3 5 9 13 17 25 33 41 49 65
  观察这个表,我们可以大胆地猜想g[i]的规律: 从1开始,加2次2,再加3次4,再加4次8,再加5次16……这个猜想的证明过程过于琐碎,详细、严谨的步骤反而会妨碍理解,我只说一下大概思路:显然g[2]是用g[1]和f[1]算出来的。假设g[i]=g[j]*2+f[i-j],那么g[i+1]=min{g[j+1]*2+f[i-j],g[j]*2+f[i-j+1]},即把f或g的下标增加1。为什么呢?因为f和g两个数列都是增长的,而且增长得越来越快。如果在最优解的基础上让f的下标变大、g的下标变小,或是相反,结果不是不变就是变大。亲手模拟一下g的计算过程,就会发现, g数列加2k的次数等于g数列加2k-1的次数与f数列加2k的次数之和。由此可以得出上面的猜想。
  这个问题可以推广到m塔的情况。用a[i]表示m柱i盘时所需的移动次数,则a数列有这样一个规律:a[1]=1,以后依次有 项比前一项大2 k,其中k从1开始取整数值。

Prob 1035: 撕邮票 回页首
  搜索、判重。判重时若保存整个图象比较每个格子则嫌慢,其实有一点小技巧:把每个图象看作一串二进制数,用一个数来存储这个图象每个格子的信息,再加上一个宽度就可以唯一地表示一个图象了。由于一个图象经翻转、旋转可得到8种形态,我们需要按某种规则只存储一个(如宽度取较长边,宽度一定时使二进制数最小)。这样既节省了时间,又节省了空间。

Prob 1036: 序列函数 回页首
  用一个队列q来存储序列,初始时设q[0]=1。设三个头指针f1,f2,f3,分别对应于三个质数p1,p2,p3(假设p1 detail 1037,"最短网络"

Prob 1037: 最短网络 回页首
  赤裸裸的最小生成树模型。

Prob 1038: 化学方程式 回页首
  思路极清晰:列多元一次方程组,用高斯消元法解。所谓高斯消元法,就是用一个含x i的方程(记为E i)与其它所有方程加减,消去其它方程中的x i,待解出x 1至x i-1后,代入E i求x i。但编程极麻烦,具体来说,有下面几点提示或注意事项:
  • 跟1117一样,列方程时最好在整个反应式最左边加个'+',倒着进行。
  • 消元时若待消的未知数已不存在,则No solution。
  • 消元时若未知数比方程多两个或两个以上,即方程过少,则No solution。
  • 消到只剩一个未知数时,方程应当一个也不剩,否则,剩下的方程中均只含最后一个未知数,它只能等于0,这是不合题意的。简言之,方程过多时也是No solution。
  • 两个方程加减后不要忘了及时约掉各系数的最大公约数,以免过程中系数过大。
  • 消元结束后,令x1=1,利用各Ei依次推出各xi。若碰到某个xi为分数,则将x1至xi-1均乘以xi的分母。若碰到某个xi<=0,则No solution。

Prob 1039: 核电站问题 回页首
  DP。用f[n]表示n个坑时的放法数,则有
  f[0]=1
  f[n]=f[n-1]*2 (1<=n  f[n]= (n>=m)
  注意到f[n-1]= (n>m),两式相减得f[n]-f[n-1]=f[n-1]-f[n-m-1] (n>m)。于是可以化简最后一个方程:
  f[m]=f[n-1]*2-1
  f[n]=f[n-1]*2-f[n-m-1] (n>m)

Prob 1040: N的倍数 回页首
  妙极了的算法:最短路。
  把自然数除以N的每个可能余数,即0~N-1,分别看成一个结点。若一个数字d是可用的,那么对任一顶点x,连一条从x到(x*10+d) mod N的有向边,边上标有数字d。然后把结点0看成两个结点0和0',用简单的BFS找一条从0到0'的最短路,路径上所有边上标的数字连起来就是答案。
  用BFS似乎只能保证答案的位数最少而不能保证值最小,其实不然。只要在扩展结点时按边上所标数字从小到大的顺序进行,答案的值也一定是最小的。

Prob 1041: 战略游戏 回页首
  • 树型DP(我的算法):首先建树,任选一个结点为根。设要看守住以x为根的子树中所有边,x处有士兵时需要的最少士兵数为yes[x],x处无士兵时需要的最少士兵数为no[x]。自下而上地计算每个结点的yes值和no值:一个结点的yes值等于它所有子树的yes值与no值的较小值之和再加1;no值等于它所有子树的yes值之和。根结点的yes值和no值中的较小值即为答案。
  • 贪心:找出所有度为1的结点,把与它们相连的结点上都放上士兵,然后把这些度为1的结点及已放上士兵的结点都去掉。重复上述过程直至树空为止。
  • 二分图最小覆盖(by HybridTheory):把树看成一个二分图,奇数层的结点放到一边,偶数层的结点放到一边,然后求这个二分图的最小覆盖数(等于最大匹配数)。

Prob 1042: 替换问题 回页首
  好奇异的算法,不知 Cocoran大牛是怎么想出来的。
  用S i表示a 1至a i的和,S' i表示b 1至b i的和。下面研究三种操作对各S i的影响:
  • 对ai-1、ai、ai+1(1i-1和Si
  • 对an、a1、a2进行操作,相当于把所有的Si都减去a1,再交换S1与Sn
  • 对an-1、an、a1进行操作,相当于把所有的Si都加上an,再交换Sn-1与Sn
  于是我们看出,无论进行多少次怎样的操作,结果只是把所有的S i都加上或减去了某一数值,然后进行了若干次交换。于是把a 1至a n、b 1至b n分别排序,若对所有的S i-S' i均相等,则YES,否则NO。

Prob 1043: 黑白棋 回页首
  简单深搜。只是面对某一个棋局时,若轮到的一方无处下子,要妥善处理pass的情况。

Prob 1046: 立方馅饼 回页首
  把x+y+z的为奇数的格子染成黑色,x+y+z为偶数的格子染成白色。若n为奇数,则Yes的充要条件是起点与终点均为黑色;若n为偶数,则Yes的充要条件是起点与终点异色。

Prob 1047: 自然数序列 回页首
  每次取数列的后四项,让两头两项为正,中间两项为负(当然反过来也行),这样这四项的和就是0了,将这四项删去。这样做到最后最多只剩3项。若剩3项或一项也不剩(即n mod 4=3或0),则答案为0,否则答案为1(因为添加负号不改变和的奇偶性,而这时所有数的和为奇数,故答案无法达到0)。

Prob 1048: Tom的烦恼 回页首
  用best[t]表示在时间t以前Tom得到的最大加工费。按时间递增的顺序计算best数组。对每个best[t],首先令其为上一个时间的best值,然后对于每一个在时间t结束的工作(设其开始时间为s,加工费为m),比较best[s]+m与best[t]的值,取大者。
  由于时间的数值可能远远大于工作的数量,因此最好首先对时间进行离散化。

Prob 1049: 砝码问题 回页首
  思路参看 1017 石子归并问题。本题的大数据规模版本就是 1166 背包问题。

Prob 1057: 办公室失窃案 回页首
  把每个时间段看成一条线段,用扫描线法找到被n条线段同时覆盖的区间,输出。当然要注意输入输出的细节。

Prob 1058: 地板覆盖问题 回页首
  老老实实地按题目说的顺序判断,即先判断所有砖中是否有交叉,再判断所有砖中是否有越界情况,最后判断地板是否全被覆盖。切不可看到第一块砖越界了就下结论,因为第二块和第三块可能交叉。两块砖(x1,y1)-(x2,y2)与(x3,y3)-(x4,y4)交叉的充要条件是(x1 detail 1059,"数的计数"

Prob 1059: 数的计数 回页首
  用f[n]表示最后一个数是n时,可以构造出的数的总数。规定f[0]=1,则显然有f[n]=f[0]+f[1]+...+f[n div 2]。
  但若直接使用这个方程,则会既TLE又MLE。注意到右边其实是f数组开头若干个元素的和,因此可开一个s数组,用s[n]来存储f[0]至f[n]的和。这样时间上显然没有问题。实际上,现在f数组已经不必要了,因为用s数组可写出如下状态转移方程:s[n]=s[n-1]+f[n]=s[n-1]+s[n div 2]。当读入n时,输出s[n div 2]即可。
  结果很大,高精度是当然的。可以用3个int64来存储一个数。这里我们看到用s数组代替f数组同样解决了MLE的问题,因为s数组的大小只有f数组的一半,题目允许的内存不能容纳f数组,却恰好可以容纳s数组。

Prob 1060: 方程的解数 回页首
  查了一下NOI的原题,发现有一个重要条件,题目中落掉了: ΣKi*MPi<=maxlongint。这个条件告诉我们,无论每个x取什么值,结果不会超过longint范围,因此不必考虑int64或是高精度之类的问题。另外,由于150 5>maxlongint,所以P不会超过4。
  这道题只能用枚举法,其瓶颈显然在于计算速度。注意到乘幂运算需屡次进行,因此很自然地想到用预处理先计算出所有的乘幂值(只有150*4=600个而已)。然而这毕竟只是细节上的优化,本质的还在于找一个高效的算法。
  把方程的半数项移到右边。得到的新方程与原方程是相似的,只是右边不再为0。但是,每一边的项数不超过3,也就是说枚举量不超过150 3=3375000,这个复杂度是完全可以承受的。我们可以枚举左边,用一个表将所有可能的结果及其出现次数存储起来,然后枚举右边,在表中查找右边结果的相反数,累加表中存储的出现次数即得到答案。现在的问题是:用什么表来存储结果呢?
  如果用有序表和二分查找的话,那么对300多万个数进行排序就需要好几秒,对同样多的数进行二分查找又需要同样多的时间,显然不可取。本题适用的数据结构是: 哈希表。哈希函数很好找:设x为结果,那么hash(x)=abs(x) mod bigprime,其中bigprime为一个取定的大质数。这样,存储和查找就几乎都是线性的了。这样处理以后,算法的时间复杂度为O(M 3),带一个适中的常数系数。
  然而本题的内存限制是很紧的,哈希表这种耗内存的数据结构一不小心就会MLE。我的经验是:
  • 不要用指针,而用数组来模拟,因为指针本身就耗内存,而且分配、回收操作也浪费不少时间。至于处理冲突的问题,可以建立公共溢出区,即把冲突的元素统统放到公共溢出区中。
  • 哈希表中无需存储原始的元素。假设原始元素为x,那么在hash表中只需存储x div bigprime+ord(x>0)。之所以要加入ord(x>0)一项,是因为如果不加,-1和1就会变成同一个数0。经过这一变换,x的范围就由原来的longint变为integer(我的bigprime取的是999983),又省了大量内存。

Prob 1061: 堆栈计算 回页首
  题目中的表达式是一个树型结构,因此想到用树型DP解决这个问题。
  把在表达式中位置为i的字符代表的结点称作结点i。首先用递归的方法找到每个B结点的左孩子的位置。结点i的左孩子用child[i]表示,显然结点i的右孩子为i-1。
  用space[i]表示计算以i为根的子树所需的最少堆栈空间,swap[j,i]表示如果有大小为j的堆栈空间可用,计算以i为根的子树所需的最少交换次数。堆栈大小不同时,这个次数是不同的,例如...BB在堆栈空间为2时需要交换1次,而在堆栈空间为3时则一次也不需要。
  按堆栈空间的大小j划分阶段,j的初值为1,这时对每个.结点i,有space[i]=1,swap[1,i]=0;对每个B结点i,有space[i]=无穷大,swap[1,i]=无穷大,这里的无穷大表示还没有算出来。
  设表达式长为l,则当space[l]=无穷大时,j加1,进行下面的状态转移。从左到右依次处理每一个结点的space值和swap值。
  • space值的计算:如果一个B结点(i)的space值为无穷大,而它的两个孩子(a、b)的space值都已算出,且有一个小于j(不妨设space[a]
  • swap值的计算:对于.结点,swap[j,i]显然为0。对于B结点(i,左右孩子分别为a、b),则swap[j,i]=min{swap[j-1,i],swap[j,a]+swap[j-1,b],swap[j,b]+swap[j-1,a]+1}。注意最后一个元素,若先计算右子树,后计算左子树,则最后需交换两个结果,因此要加1。
  注意到在计算swap数组的第j行时只用到了第j-1行,因此swap数组可用滚动数组来实现。

Prob 1068: 商务旅行 回页首
MAIGO的同济题解1_第1张图片
处理结点10的时候并查集的情况
(假设结点是按从左到右的顺序处理的)
与10的LCA是10的结点集合:{10}
与10的LCA是8结点集合:{8 9 11}
与10的LCA是3的结点集合:{3 7}
与10的LCA是1的结点集合:{1 2 5 6}
不属于任何集合的结点:4 12
   感谢Faint.Wisdom讲解求最近公共祖先(LCA)的Tarjan算法!下面是算法的详细讲解:
  首先,Tarjan算法是一种离线算法,也就是说,它要首先读入所有的询问(求一次LCA叫做一次询问),然后并不一定按照原来的顺序处理这些询问。而打乱这个顺序正是这个算法的巧妙之处。看完下文,你便会发现,如果偏要按原来的顺序处理询问,Tarjan算法将无法进行。
  Tarjan算法是利用并查集来实现的。它按DFS的顺序遍历整棵树。对于每个结点x,它进行以下几步操作:
  • 计算当前结点的层号lv[x],并在并查集中建立仅包含x结点的集合,即root[x]:=x。
  • 依次处理与该结点关联的询问。
  • 递归处理x的所有孩子。
  • root[x]:=root[father[x]](对于根结点来说,它的父结点可以任选一个,反正这是最后一步操作了)。
  现在我们来观察正在处理与x结点关联的询问时并查集的情况。由于一个结点处理完毕后,它就被归到其父结点所在的集合,所以在已经处理过的结点中(包括x本身),x结点本身构成了与x的LCA是x的集合,x结点的父结点及以x的所有已处理的兄弟结点为根的子树构成了与x的LCA是father[x]的集合,x结点的父结点的父结点及以x的父结点的所有已处理的兄弟结点为根的子树构成了与x的LCA是father[father[x]]的集合……(上面这几句话如果看着别扭,就分析一下句子成分,也可参照右面的图)假设有一个询问(x,y)(y是已处理的结点),在并查集中查到y所属集合的根是z,那么z就是x和y的LCA,x到y的路径长度就是lv[x]+lv[y]-lv[z]*2。累加所有经过的路径长度就得到答案。
  现在还有一个问题:上面提到的询问(x,y)中,y是已处理过的结点。那么,如果y尚未处理怎么办?其实很简单,只要在询问列表中加入两个询问(x,y)、(y,x),那么就可以保证这两个询问有且仅有一个被处理了(暂时无法处理的那个就pass掉)。而形如(x,x)的询问则根本不必存储。
  如果在并查集的实现中使用路径压缩等优化措施,一次查询的复杂度将可以认为是常数级的,整个算法也就是线性的了。
  另外,实现上还有一点技巧:树的边表和询问列表的存储可以用数组模拟的链表来实现。

Prob 1071: 一道简单题-版本1 回页首
  用s[i]表示前i个数之和。从1到n枚举累加段的右端j,当j一定时,我们的问题就是找一个i(j-l2<=i<=j-l1),使s[j]-s[i]最大,即s[i]最小。于是我们想到了堆。维护一个堆,使堆中任一对父子结点x,y(x为父结点),都有s[x]<=s[y]。在右端枚举到j时,若j>=l1,那么就把j-l1放到堆里。这时考察堆顶元素(记作i):若i=j-l2。这时得到累加段右端为j时的最大和s[j]-s[i],与当前最优解比较,取最大值。
  这种算法的时间复杂度为O(nlogn),需要开两个大小为n的数组。这个空间复杂度对版本2是行不通的。其实,本题还有更优的算法,只是我在做本题时没有想到。详见 版本2。

Prob 1074: 镜子迷宫 回页首
  本题数据范围很小(这是当然的),所以可以用分层图BFS解决。
  分层图的每一层对应着所有镜子的一个状态。不妨用二进制给每个状态编号:假设共有5个镜子,其中3、5号镜子被旋转了,则可用10100 2=20 10表示这个状态。于是,我们首先需要求出在每一层中,有哪些位置是不可到达的。这一过程很简单:在确定每个镜子的方向后,从每个发射器出发,把不可到达的位置依次做上标记即可。
  然后,从初始层的起点出发,进行BFS。扩展队列有两种方法:一是向四个方向之一前进一步,二是旋转某个镜子,即进入另一层。假设原来在第10100 2层,旋转了第2个镜子,那么就来到了第10100 2 xor 00010 2=10110 2层。如果搜到了任一层的终点,则成功。

Prob 1075: 一道简单题-版本2 回页首
  A simple prob isn't usually an easy prob.
  首先提出一个性质:如果某一段开头一部分的和<=0,那么把这一段删去后,剩余部分的和不会比原来小。
  设置两个指针p和i,p分别指向累加段的左端的前一位置,i指向累加段的右端。再设一个变量s,保存当前的累加和。初始时,p=0,i=l1,s=前l1个数之和。然后将i不断移直至n。记i-l1=j。对于i的每一个位置,第j个数以后的数是必须取的,而第j个数及以前的数都是可取可不取的(以下称第j个数及以前的数为 自由段)。若自由段开头某一段的和<=0,那么这一段就可以删去了。因此可另开一个累加器s0,每次把i后移一个位置,在s上加上第i个数,在s0上加上第j个数。若某时刻s0<=0,则p:=j;dec(s,s0);s0:=0。
  但我们又遇到了一个问题:我们只有在自由段的开头有一段的和<=0时才缩短累加段,那么如果累加段的长度已经达到了l2,而自由段开头任一段的和均为正值怎么办?当i再后移时,必须删掉自由段开头的一部分了!当然,我们还是希望删掉部分的和越小越好。那么怎么知道加到哪里的和最小呢?
  我们不再使用累加器s0,而是把它换成两个队列a和b。队列中存储的是把自由段分成的若干段,a[x]表示一段的和,b[x]表示一段的结束位置。下面用r表示队尾。i每后移一个位置,就往s上累加第i个数,同时把第j个数连同它的位置一同放到队尾:inc(r);a[r]:=第j个数;b[r]:=j。若a[r]<=0,那么:
  • 若队列中只有一段,那么这一段就是自由段开头的一段,因为它的和<=0,故把它删去即可。
  • 若队列中不只这一段,那么可以把这一段与上一段合并:inc(a[r-1],a[r]);b[r-1]:=b[r];dec(r),因为反正这一段的和<=0,若删掉最后这一段之前的所有段,不如把这一段一起删掉。重复合并直至队列中只剩一段或a[r]>0为止。
  删除某一段时,令p为这一段的b值,并从s中减去这一段的和。比较i在每个位置时的s值,其中最大值就是答案。
  这种算法解决了累加段长度达到l2时,自由段开头没有和<=0的一段的问题:因为这时队列中所有段的a值均为正,所以仅删第一段的损失最小。此算法的时间复杂度为O(n),优于 版本1中所述。
  然而现在内存上还有一个严峻的问题:每个队列的大小最大可能为n,开两个大小为n的longint数组就要耗用4*300000*2=2400KB的内存,MLE了。也许你会想:b数组存放的是位置,它的大小不会超过n,因此存储每个数用3个字节就够了;而题目中说中间结果及最后结果绝对值都不超过100000,a数组每个数用3个字节似乎也行。但实际上本题可以只开一个数组的:对队列中的每一段,若它的长度为1,那么它的b值不必存储,可利用上一段的b值加1间接得出(“第一段的上一段的b值”就是p),因此队列中只存它的a值;若它的长度>1,则只能把a、b两值都存下(我的程序中把b值放在前面)。那么怎么区分队列中的值哪些是a值,哪些是b值呢?注意到所有的a值都是正的(除非队列中只有一段),因此把所有的b值存为它的相反数即可。这样做以后,长度为1的段在队列中占用一个longint,更长的段占用2个longint,也就是说总共只需n个longint。尽管这样做队列的操作会麻烦一些。程序请见ac1075a.pas。

  P.S.楼天城大牛有不用数组的算法,程序运行的时间也只有我的1/3——25ms左右。他是怎样做的我就不知道了(-_-b)

  一种更简单的O(n)算法:
  考虑前i个数的和——部分和s[i]。把s[i]-s[j]中的i看作阶段,j看作决策。开一个决策队列,使队列中的决策具有部分和递增的性质。当处理到第i个数时,把过时(i-j>l2)的决策从队首删除,并把新产生的决策(j=i-l1)放到队尾。若决策部分和的递增性被破坏,即倒数第二个决策优于最后一个决策,则倒数第二个决策永远不会最优。不断删除倒数第二个决策直至部分和递增性成立。这时队首决策为当前最优决策。
  ac1075b.pas存放了这个算法的程序。空间问题我是采用以3个字节表示一个数的方法解决的,因为这样编程复杂度较低。


Prob 1077: 挑战 回页首
  首先算出彩虹可以挨打的次数a和猫老大可以挨打的次数b。
  比较容易想到的算法是DP:设f[a,b]为猫老大的胜率,则f[a,b]=(f[a,b-1]+f[a-1,b])/2,边界情况为f[0,x]=1,f[x,0]=0(x为正整数)。但是,a和b的最大值均为32767,在极端情况下DP会超时。
  然后想到组合的方法。猫老大若想取胜,则在前a+b-1个回合中,他至少要进攻a次。那么猫老大的胜率= 。之所以化成第二种形式是为了方便计算,是因为第二种形式中和式的每一项的计算过程中只用了乘除法,可以用对数计算,而结果不超过1,可以直接相加。另外,不必每个组合数都重新计算,计算一个组合数时,在上一个的基础上乘以一个数再除以一个数即可。这样设计出的算法是线性的,虽然每次运算慢了点。

Prob 1079: 赌博游戏 回页首
  典型的博弈问题,寻找猫的必败态。显然,0是必败态。观察每次可以拿的黄金的块数,发现它们都不能被3整除,因此,3的倍数都是必败态。因此,如果开始时黄金块数是3的倍数,则王胜,否则猫胜,第一次拿走黄金块数除以3的余数即可。因为10 mod 3=1,所以求一个数除以3的余数只要把所有数位相加,这个和除以3的余数就是原数除以3的余数。

Prob 1080: 礼物 回页首
  枚举左右两边(不要忘了整块布的左右边界),然后在左右两边确定的竖条中用扫描线法找能切出的高度最大的手绢。可在预处理中把所有的上下边排一下序,做扫描线时直接利用。

Prob 1081: 猫老大数 回页首
  先用预处理求出sqrt(maxlongint)以内的质数表。对一个给定的数n,若n正好是某个质数的平方,那么它是一个猫老大数。以下讨论n不是某质数平方的情况:
  • 一个猫老大数分解质因数后的形式为n=p*q(p
  • 若n不是猫老大数,则:
    • n可能等于1,这时n在2至sqrt(n)范围内无质因数;
    • n可能本身就是质数,这时n在2至sqrt(n)范围内亦无质因数;
    • n有多于2个(不是2种)质因数,这时n在2至sqrt(n)范围内的质因数必然多于1个(不是1种)。这点可由反证法证明:假设n在2至sqrt(n)范围内的质因数只有1个,那么其余的2个或2个以上质因数就都大于sqrt(n),其积必大于n,而是这不可能的。
  由上可以得出不是质数平方的n是猫老大数的充要条件:对所有2至sqrt(n)的质数p,累加n中因数p的个数。若累加和恰为1,那么n是猫老大数,否则不是。

Prob 1082: 最远距离点对 回页首
  首先,距离最远的两个点一定都在输入的点的凸包上。简证如下:设距离最远的两个点为A和B,旋转图形使B点位于A点正上方。如果还有比B点更高的点C,那么AC一定大于AB,这与AB距离最远矛盾。所以B点一定是最高点,也就一定在凸包上。同理可证A点也一定在凸包上。
  TJU上的数据很弱,所以求出凸包以后,枚举凸包上的点对就可以AC了。但是,当输入的点全部分布在凸包上时,用枚举法就会超时了。下面介绍一种“对踵点法”,求凸包以后的步骤的时间复杂度仅为O(n)。
  先给出“ 对踵点”的定义:如果过凸包上的两个点可以画一对平行直线,使凸包上的所有点都夹在两条平行线之间或落在平行线上,那么这两个点叫做一对对踵点。下面将证明最远距离点对一定是一对对踵点。
  观察图1,设∠1+∠2≤180°,∠3+∠4≤180°。那么如果∠1≥∠4,则由于∠1+∠2≤180°,一定可过B、E作两条平行线使边AB落在其中一条上;反之,则可过B、E作两条平行线使DE落在其中一条上。也就是说,当一条线段截凸包所成的两组“同旁内角”之和均不超过180°时,这条线段的两个端点是一对对踵点。因此,若一条线段的两个端点不是对踵点,则必有一组同旁内角之和大于180°。
  再观察图2。因为有一组同旁内角之和大于180°,所以这两个角中必有一个角大于90°(不妨设∠ABE>90°)。这种情况下显然有AE>BE,因此BE不是最远距离点对。也就是说,不是对踵点的点对一定不是最远距离点对,所以最远距离点对一定是对踵点。
  很显然地,过一对对踵点一定可以作两条平行线,使凸包的一条边落在一条直线上。因此,我们设两个指针,一条指向凸包的某条边,另一个指针指向离这条边最远的点。这条边的两个端点与这个点都是对踵点,因此都可能是最远距离点对。按顺序移动边指针并相应地移动点指针,当两个指针绕凸包转了一圈以后,答案就出来了。

Prob 1084: n因子最小正整数 回页首
  谢谢 Waterfish指出我贪心算法的错误。其实这个题的正宗解法还是搜索。
  把一个数(大于1)分解质因数:a=p 1 k1*p 2 k2*...*p n kn,则a的约数个数为(k 1+1)*(k 2+1)...*(k n+1)。显然,在约数个数相同的情况下,要使a最小,应有k 1>=k 2>=...>=k n。因此,我们可以搜索每个质因数的指数,把上面的不等式当作剪枝条件。质因数只需用前trunc(log 2100000)=16个即可。
  搜索出n因子最小正整数的质因数分解式后,剩下的就是高精度乘方和乘法了。计算乘方时,不要一个一个地把指数个底数相乘,这样做太慢。比较快的方法是二分递归,比如计算2 9,可以这样:2 9=(2 4) 2*2,其中2 4=(2 2) 2,而2 2=(2 1) 2,2 1为已知。

Prob 1085: 递增序列 回页首
  记输入序列为s,其长度为l。用nonzero[i]表示s[i..l]中第一个非零数的位置,规定s[l]=l。
  既然要求最后一个数最小,那么就从l downto 1枚举最后一个数开始的位置,设当前枚举到k。左边的序列用DP解决。next[i]的含义如下:
  • 如果在第i个数字处开始不是最后一个数,那么next[i]表示下一个数开始的位置;
  • 如果在第i个数字处开始的数是最后一个数,则next[i]=l+1;
  • 如果自第i个数字起的序列无法划分,则next[i]=0。
从k-1 downto 1枚举i,依次计算next[i]。next[i]应满足的条件是:
  1. next[next[i]]>0,即从next[i]开始的序列可以划分;
  2. s[i..next[i]-1]表示的数小于s[next[i]..next[next[i]]-1]表示的数,即严格递增;
  3. next[i]尽可能大。
因此可从k downto i+1依次枚举next[i],一旦有某个next[i]满足前两个条件,则next[i]就已算出;若k downto i+1的所有值都不满足前两个条件,则next[i]=0。若最后next[1]不为0,说明有解,按图索骥地输出,否则dec(k),继续循环。
  但若完全按照上述方法来做,程序在遇到3203400999这个数据时会输出3,203,400,999,而显然最优解应是320,340,0999。也就是说,此程序在枚举k时并没有考虑到前导的0。为解决这个问题,我们在枚举i时加上一点判断:如果从s[i]到s[k-1]的序列全为数字0,即nonzero[i]=k,那么直接令next[i]=l+1,否则再按上文所述计算next[i]。而在s[k]='0'时,直接跳过此次循环即可。
  枚举计算next[i]时还有一点优化:初值不必一律取k。因为第一个数的位数无论如何不会超过最后一个数的位数,因此初值取k与nonzero[i]+l+1-k的较小值即可。
  经过这两点改进,发现对于全0序列程序不会给出任何输出。这是因为任一s[k]均等于0,循环均被跳过。因此对于全0序列要特殊处理。

Prob 1087: 垃圾陷阱 回页首
  在以下的叙述中,g[i].time、g[i].life、g[i].height分别表示第i个垃圾下落的时间、吃下后可以增加的寿命、垫高的高度;update(a,b)表示if b>a then a:=b。
  这道题可以用DP解决。用l[i,j]表示掉下来i个垃圾后,卡门爬上的高度为j时她最长的寿命。显然l[0,0]=10。对于任一个状态l[i-1,j],若l[i-1,j]>=g[i].time,说明这个状态的卡门能够坚持到下一个垃圾下落。在这种情况下,按以下两种方法处理第i个垃圾,即进行状态转移:
  1. 吃掉第i个垃圾,即update(l[i,j],l[i-1,j]+g[i].life);
  2. 用第i个垃圾来垫高。令t=j+g[i].height,即把第i个垃圾用来垫高后卡门爬上的总高度。如果t>=d,则卡门于g[i].time时爬了出来,否则update(l[i,t],l[i-1,j])。
  若首次遇到某一个l[i,0]一次也没有赋值,说明卡门不可能坚持到第i个垃圾下落,则她最多可以存活的时间为l[i-1,0](即把前i-1个垃圾全吃掉后的寿命)。
  注意到在计算l数组的第i行时只用到了第i-1行,因此l数组可用滚动数组来实现。

Prob 1090: Symbol树 回页首
  首先澄清一下多叉树的概念:多叉树的子树是有顺序的。
  用f[n]表示有n个结点的多叉树的数目,规定f[0]=1,则 ,其中i表示第一棵子树中结点的数目。

Prob 1094: 统计三角形个数 回页首
  首先看到题目中顶点的编号方式不利于解题,故将其转换为坐标表示法,如原来的顶点8转换为第4行第2列,即(4,2)。转换方法如下:设顶点x的上方有t行,则t是方程t*(t+1)/2=x-1的正数解的整数部分(体会一下方程右边-1的巧妙性)。于是,顶点x便转换为(t+1,x-t*(t+1) div 2)。
  下面进入正题。读入被删边的时候,把这些边分为两类:水平的和斜的。在以下的叙述中,把朝上的三角形的上顶点及朝下的三角形的下顶点叫做三角形的 顶点。依次统计顶点为(x,y)(1<=y<=x<=n)的三角形。
  在统计顶点为(x,y)的三角形时,设两个变量u、d,分别表示三角形顶点的对边所在行号的最小值和最大值,初始时u=min{y-1,x-y},d=n。然后根据被删边来确定以(x,y)为顶点的三角形中哪些仍然完整:
  1. 假若某一条斜的被删边正好处于从(x,y)出发的两条斜直线上,那么顶点的对边在这条被删边以上(如果它在(x,y)的上方)或以下(如果它在(x,y)的下方)的三角形都不复存在了。因此,使用斜的被删边来缩小u~d的范围。
  2. 现在,u~x-1和x+1~d的每一个值都对应着一个以(x,y)为顶点的三角形。下面就检查一下这些三角形是否被水平的被删边破坏掉。最后剩下的三角形就是图中存在的三角形,累计即可。

Prob 1095: Feli的生日礼物 回页首
  再用一遍 1014中的化学术语:“一个2跟一个5反应生成一个0,因为2过量,所以照5算”。也就是说,因数5应是我们关注的对象。
  举个例子来说明我的算法:假设要计算33!,首先把1至33的不是5的倍数的数的末位数相乘,取末位数。而剩下没有乘的就是5,10,15,20,25,30,共6个(33 div 5=6)。把6个约数5提出来留到最后一起乘,剩下的又是计算6!,于是便看出这是一个递归过程,这也就是算法的框架。
  具体实现中有以下几个小窍门:
  • 计算1至n的不是5的倍数的数之积的末位数可以const,因为n的末位数与积的末位数之间存在对应关系。当然,n=0和n=1的情况例外。
  • 因为乘以5之前和之后的末位数都是偶数,所以乘以5的效果与乘以8相同,即:2*5->6,4*5->2,6*5->8,8*5->4。
  • 显然这道题需要用高精度。不要图省事而用字符串,因为字符串与数组相比,delete(s,1,1)这个操作太费时间。而且,我的经验表明,用字符串会莫名其妙地WA掉。

Prob 1099: 破解平方数 回页首
  子集中元素之积为平方数,也就是对于任一质数p,子集中每一个数包含p的个数之和为偶数。用布尔变量b[i]表示子集是否包含第i个数,布尔变量a[x,i]表示第i个数是否含有奇数个第x个质数。可以列出以下布尔方程组:
a[1,1] and b[1] xor a[1,2] and b[2] xor ... xor a[1,m] and b[m] = false
a[2,1] and b[1] xor a[2,2] and b[2] xor ... xor a[2,m] and b[m] = false
... ...
a[t,1] and b[1] xor a[t,2] and b[2] xor ... xor a[t,m] and b[m] = false
其中b[i]是未知数。本题的答案,就是上述方程组的解的组数减1(空集不符合题意)。注意到所有方程的右边均为false,因此这个方程组不会无解。
  那么如何求得这个方程组的解的组数呢?可以用高斯消元法。依次消去每一个未知数。消未知数b[i]时,若它的所有系数均为false,那么它是一个 自由变量,也就是说,它是取true还是取false都可以,同时它已经不存在了,不必消去;否则找一个b[i]的系数不为false的方程,用它与其它每一个方程进行异或运算,再把这个方程本身删除,这样就消去了b[i]。因为当自由变量取特定值时,每个非自由变量都可由自由变量根据消去这个非自由变量时与其它方程异或的方程算出,所以方程组的解的组数就是2^自由变量个数。
乥 僽儘乕僪僶儞僪椏嬥斾妑 亂忋栰僋儕僯僢僋亃24帪娫柍椏儊乕儖憡択 乥 嶰榓僼傽僀僫儞僗侓娙扨僉儍僢僔儞僌侓 乥

你可能感兴趣的:(MAIGO的同济题解1)