Tongji Online Judge Solutions Welcome to
Tongji Online Judge Solutions by Maigo Akisame Volume 1

我在TJU的所有AC程序及本题解均可在purety.jp/akisame/oi/TJU.rar下载。

  放心大胆地做吧,不会TLE:)   第一问即经典的最长不上升子序列问题,可以用一般的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(n2)。
  第二问用贪心法即可。每颗导弹来袭时,使用能拦截这颗导弹的防御系统中上一次拦截导弹高度最低的那一套来拦截。若不存在符合这一条件的系统,则使用一套新系统。
  注意:第二问不可以用每次删除最长不上升序列的方法做。比如,当来袭的导弹共有4颗,高度依次为2 4 1 3的时候,如果不巧找到的最长不上升序列为4 1,那么剩下的2和3就只能另外启用两套系统了。这显然不是最优解。   用a[i]表示第i年牛的总数,则a[i]=a[i-1]+a[i-3],即3年前所有的牛所生的小牛加上上一年所有的牛。   挨个检查即可。   把三角形的下角拖到右面去,便可以编出连数组都不用的程序了:)   用筛法求素数即可。关键是怎么把求得的素数都保存下来。可以用1个字节保存8个二进制位,即8个数是否为素数。再用count[i]表示i*8以内素数的个数(前缀表示法)。求m到n之间素数个数时,除了用两个count值相减外(前缀表示法的常用技巧),还要注意处理一下m和n附近的素数。   在乘的过程中,我们只需保留最后一位非零数。这位数在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。
  用数组模拟链表即可。   每一个保留的数字都从可取的范围中取最小的一位即可。用a[i]表示第i个保留的数字在原数中的位置,设a[0]=0,则a[i]的范围为a[i-1]+1~n-m+i。   借用化学术语说,“一个2跟一个5反应生成一个0,因为2过量,所以照5算”。n!末尾零的个数就是1~n这n个数中因数5的个数,即n div 5+n div 52+n div 53...直到某项减少至0为止。   用实数乘,超过100就除以10,最后取个位即可。   开一个最小下标为0,最大下标为最大总质量的一半(maxw)的布尔数组b,初始时仅b[0]为true。依次读入每一个石子的质量。设当前石子的质量为m,则对于原来的任一个值为true的b[i](0<=i<=maxw-m),令b[i+m]为true。处理完每个石子后,设值为true的b值中最大下标为x,则x就是把石子按质量尽可能平均地分为两堆后较轻的那一堆的质量。   垃圾题。不要按照一般的乘法竖式输出,而要观察样例程序的输出,然后照葫芦画瓢……SPOJ上有一道Simple Arithmetics,比这个题好得多,推荐做一下。   说是麻将游戏,其实就是连连看嘛。好像有不少人看不懂题意,可以Google一个连连看玩一下。
  这个题的解法就是简单BFS。从起点出发,每次扩展就是沿上下左右四个方向闯下去,直到碰到一张牌或边界为止。这里的“边界”应是原棋盘再往外的一圈,因为连线可以超出棋盘。如果碰到的牌正好是终点,那么BFS成功。
  另外还要注意,输出的应是路径上线段的条数,而不是拐的弯数。   初学者的基础题,我就不讲算法了。要说的是题目叙述的第一行不应是“A$='mp'”,而应是“A$='m<n>p'”,<n>被当成HTML标签吃掉了。看一下样例你就不会迷茫了:)   这个题是TJU上一道极垃圾的题。虽然是Special Judge,但实际上能AC的只有按升序排列在最前面的那一个三角形。这样看来,连样例输出都是错的:(   深搜。为了加快速度,可以不用布尔数组记录一个数是否用过,而用数组模拟的链表。   注意到k,a,x的范围其实都只是0~64,于是枚举即可。   本题若用BFS,则每个结点可扩展的子结点数会巨大,空间复杂度无法承受。因此采用DFS-ID,即先假设解的深度(单位分数的个数)为1,进行DFS,若不成功,再假设解的深度为2,DFS……直到找到解为止。
  DFS-ID解决了空间问题,但还有一个问题就是每个结点扩展子结点的范围,因为单位分数的分母没有限制,必须人为找一个限制条件。   上述过程中,为计算准确及在叶子结点处获得剩余分数的分母方便,剩余分数不要采用实数类型计算,而是设两个整型变量分别表示分子和分母。

  注:本题的题目描述不甚清楚。在多解的情况下,题目只说要分数个数最少,分数个数相同的情况下最大的分母最小。但是,若最大的分母还相同怎么办呢?比如8/27,是分解成1/4+1/36+1/54,还是分解成1/6+1/9+1/54?对此,我的程序输出的是找到的第一个解4 36 54,亦即认为在最大的分母还相同的情况下,取字典顺序最小的一个。这可能不是出题者的原意,但用这种标准,我AC了。   假设我们要求kensta的序号,那么我们只要求出以a开头的、以e开头的、以ka开头的……字符串各有多少,累加就行了。剩下的任务就是已知一些字符,求它们可以组成多少个不同的字符串。这是一个重排列问题。假设共有n种字符,其中字符ci有ai个,那么求这些字符可以组成的不同字符串数的公式为   宽搜即可,因为总状态数只有(10*10*4)2=160000。   先用预处理算出符号的所有填法对应的结果,然后问哪个输出哪个就行。   此题的确定算法,不是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次),就基本可以保证得到最优解了。   我们把利用三个、四个柱子移动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<=j<i,i>1)
  然而仅仅利用这个式子,算法复杂度是O(n2)的,对于n<=50000的数据范围,显然无法承受。因此,我们列出i值较小时的f[i]和g[i],以期发现某种规律。
i1234567891011
f[i]13715316312725551110232047
g[i]135913172533414965
  观察这个表,我们可以大胆地猜想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,以后依次有项比前一项大2k,其中k从1开始取整数值。   搜索、判重。判重时若保存整个图象比较每个格子则嫌慢,其实有一点小技巧:把每个图象看作一串二进制数,用一个数来存储这个图象每个格子的信息,再加上一个宽度就可以唯一地表示一个图象了。由于一个图象经翻转、旋转可得到8种形态,我们需要按某种规则只存储一个(如宽度取较长边,宽度一定时使二进制数最小)。这样既节省了时间,又节省了空间。   用一个队列q来存储序列,初始时设q[0]=1。设三个头指针f1,f2,f3,分别对应于三个质数p1,p2,p3(假设p1<p2<p3)。头指针的初值均为0。每次取q[f1]*p1,q[f2]*p2,q[f3]*p3中的最小值,如果当前队列为空或它与当前队尾元素不同,则将其放进队列。无论是否放进队列,都将产生最小值的头指针加1。当队列达到所需的长度时,输出队尾元素。   赤裸裸的最小生成树模型。   思路极清晰:列多元一次方程组,用高斯消元法解。所谓高斯消元法,就是用一个含xi的方程(记为Ei)与其它所有方程加减,消去其它方程中的xi,待解出x1至xi-1后,代入Ei求xi。但编程极麻烦,具体来说,有下面几点提示或注意事项:   DP。用f[n]表示n个坑时的放法数,则有
  f[0]=1
  f[n]=f[n-1]*2 (1<=n<m)
  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)   妙极了的算法:最短路。
  把自然数除以N的每个可能余数,即0~N-1,分别看成一个结点。若一个数字d是可用的,那么对任一顶点x,连一条从x到(x*10+d) mod N的有向边,边上标有数字d。然后把结点0看成两个结点0和0',用简单的BFS找一条从0到0'的最短路,路径上所有边上标的数字连起来就是答案。
  用BFS似乎只能保证答案的位数最少而不能保证值最小,其实不然。只要在扩展结点时按边上所标数字从小到大的顺序进行,答案的值也一定是最小的。   好奇异的算法,不知Cocoran大牛是怎么想出来的。
  用Si表示a1至ai的和,S'i表示b1至bi的和。下面研究三种操作对各Si的影响:   于是我们看出,无论进行多少次怎样的操作,结果只是把所有的Si都加上或减去了某一数值,然后进行了若干次交换。于是把a1至an、b1至bn分别排序,若所有的Si-S'i均相等,则YES,否则NO。   简单深搜。只是面对某一个棋局时,若轮到的一方无处下子,要妥善处理pass的情况。   把x+y+z的为奇数的格子染成黑色,x+y+z为偶数的格子染成白色。若n为奇数,则Yes的充要条件是起点与终点均为黑色;若n为偶数,则Yes的充要条件是起点与终点异色。   每次取数列的后四项,让两头两项为正,中间两项为负(当然反过来也行),这样这四项的和就是0了,将这四项删去。这样做到最后最多只剩3项。若剩3项或一项也不剩(即n mod 4=3或0),则答案为0,否则答案为1(因为添加负号不改变和的奇偶性,而这时所有数的和为奇数,故答案无法达到0)。   用best[t]表示在时间t以前Tom得到的最大加工费。按时间递增的顺序计算best数组。对每个best[t],首先令其为上一个时间的best值,然后对于每一个在时间t结束的工作(设其开始时间为s,加工费为m),比较best[s]+m与best[t]的值,取大者。
  由于时间的数值可能远远大于工作的数量,因此最好首先对时间进行离散化。   思路参看1017 石子归并问题。本题的大数据规模版本就是1166 背包问题   把每个时间段看成一条线段,用扫描线法找到被n条线段同时覆盖的区间,输出。当然要注意输入输出的细节。   老老实实地按题目说的顺序判断,即先判断所有砖中是否有交叉,再判断所有砖中是否有越界情况,最后判断地板是否全被覆盖。切不可看到第一块砖越界了就下结论,因为第二块和第三块可能交叉。两块砖(x1,y1)-(x2,y2)与(x3,y3)-(x4,y4)交叉的充要条件是(x1<x4) and (x3<x2) and (y1<y4) and (y3<y2)。第三步判断砖是否盖满地板时,可以累加所有砖的面积,若这个和与地板的面积相等,则盖满。   用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数组。   查了一下NOI的原题,发现有一个重要条件,题目中落掉了:ΣKi*MPi<=maxlongint。这个条件告诉我们,无论每个x取什么值,结果不会超过longint范围,因此不必考虑int64或是高精度之类的问题。另外,由于1505>maxlongint,所以P不会超过4。
  这道题只能用枚举法,其瓶颈显然在于计算速度。注意到乘幂运算需屡次进行,因此很自然地想到用预处理先计算出所有的乘幂值(只有150*4=600个而已)。然而这毕竟只是细节上的优化,本质的还在于找一个高效的算法。
  把方程的半数项移到右边。得到的新方程与原方程是相似的,只是右边不再为0。但是,每一边的项数不超过3,也就是说枚举量不超过1503=3375000,这个复杂度是完全可以承受的。我们可以枚举左边,用一个表将所有可能的结果及其出现次数存储起来,然后枚举右边,在表中查找右边结果的相反数,累加表中存储的出现次数即得到答案。现在的问题是:用什么表来存储结果呢?
  如果用有序表和二分查找的话,那么对300多万个数进行排序就需要好几秒,对同样多的数进行二分查找又需要同样多的时间,显然不可取。本题适用的数据结构是:哈希表。哈希函数很好找:设x为结果,那么hash(x)=abs(x) mod bigprime,其中bigprime为一个取定的大质数。这样,存储和查找就几乎都是线性的了。这样处理以后,算法的时间复杂度为O(M3),带一个适中的常数系数。
  然而本题的内存限制是很紧的,哈希表这种耗内存的数据结构一不小心就会MLE。我的经验是:   题目中的表达式是一个树型结构,因此想到用树型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值。   注意到在计算swap数组的第j行时只用到了第j-1行,因此swap数组可用滚动数组来实现。

处理结点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,它进行以下几步操作:   现在我们来观察正在处理与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)的询问则根本不必存储。
  如果在并查集的实现中使用路径压缩等优化措施,一次查询的复杂度将可以认为是常数级的,整个算法也就是线性的了。
  另外,实现上还有一点技巧:树的边表和询问列表的存储可以用数组模拟的链表来实现。
  用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,那么现在i是不合法的,以后也不会合法,把它从堆中删除。不断删除堆顶元素直至i>=j-l2。这时得到累加段右端为j时的最大和s[j]-s[i],与当前最优解比较,取最大值。
  这种算法的时间复杂度为O(nlogn),需要开两个大小为n的数组。这个空间复杂度对版本2是行不通的。其实,本题还有更优的算法,只是我在做本题时没有想到。详见版本2   本题数据范围很小(这是当然的),所以可以用分层图BFS解决。
  分层图的每一层对应着所有镜子的一个状态。不妨用二进制给每个状态编号:假设共有5个镜子,其中3、5号镜子被旋转了,则可用101002=2010表示这个状态。于是,我们首先需要求出在每一层中,有哪些位置是不可到达的。这一过程很简单:在确定每个镜子的方向后,从每个发射器出发,把不可到达的位置依次做上标记即可。
  然后,从初始层的起点出发,进行BFS。扩展队列有两种方法:一是向四个方向之一前进一步,二是旋转某个镜子,即进入另一层。假设原来在第101002层,旋转了第2个镜子,那么就来到了第101002 xor 000102=101102层。如果搜到了任一层的终点,则成功。   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,那么:   删除某一段时,令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个字节表示一个数的方法解决的,因为这样编程复杂度较低。   首先算出彩虹可以挨打的次数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,可以直接相加。另外,不必每个组合数都重新计算,计算一个组合数时,在上一个的基础上乘以一个数再除以一个数即可。这样设计出的算法是线性的,虽然每次运算慢了点。   典型的博弈问题,寻找猫的必败态。显然,0是必败态。观察每次可以拿的黄金的块数,发现它们都不能被3整除,因此,3的倍数都是必败态。因此,如果开始时黄金块数是3的倍数,则王胜,否则猫胜,第一次拿走黄金块数除以3的余数即可。因为10 mod 3=1,所以求一个数除以3的余数只要把所有数位相加,这个和除以3的余数就是原数除以3的余数。   枚举左右两边(不要忘了整块布的左右边界),然后在左右两边确定的竖条中用扫描线法找能切出的高度最大的手绢。可在预处理中把所有的上下边排一下序,做扫描线时直接利用。   先用预处理求出sqrt(maxlongint)以内的质数表。对一个给定的数n,若n正好是某个质数的平方,那么它是一个猫老大数。以下讨论n不是某质数平方的情况:

  由上可以得出不是质数平方的n是猫老大数的充要条件:对所有2至sqrt(n)的质数p,累加n中因数p的个数。若累加和恰为1,那么n是猫老大数,否则不是。   首先,距离最远的两个点一定都在输入的点的凸包上。简证如下:设距离最远的两个点为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不是最远距离点对。也就是说,不是对踵点的点对一定不是最远距离点对,所以最远距离点对一定是对踵点。
  很显然地,过一对对踵点一定可以作两条平行线,使凸包的一条边落在一条直线上。因此,我们设两个指针,一条指向凸包的某条边,另一个指针指向离这条边最远的点。这条边的两个端点与这个点都是对踵点,因此都可能是最远距离点对。按顺序移动边指针并相应地移动点指针,当两个指针绕凸包转了一圈以后,答案就出来了。   谢谢Waterfish指出我贪心算法的错误。其实这个题的正宗解法还是搜索。
  把一个数(大于1)分解质因数:a=p1k1*p2k2*...*pnkn,则a的约数个数为(k1+1)*(k2+1)...*(kn+1)。显然,在约数个数相同的情况下,要使a最小,应有k1>=k2>=...>=kn。因此,我们可以搜索每个质因数的指数,把上面的不等式当作剪枝条件。质因数只需用前trunc(log2100000)=16个即可。
  搜索出n因子最小正整数的质因数分解式后,剩下的就是高精度乘方和乘法了。计算乘方时,不要一个一个地把指数个底数相乘,这样做太慢。比较快的方法是二分递归,比如计算29,可以这样:29=(24)2*2,其中24=(22)2,而22=(21)2,21为已知。
  记输入序列为s,其长度为l。用nonzero[i]表示s[i..l]中第一个非零数的位置,规定s[l]=l。
  既然要求最后一个数最小,那么就从l downto 1枚举最后一个数开始的位置,设当前枚举到k。左边的序列用DP解决。next[i]的含义如下:从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序列要特殊处理。   在以下的叙述中,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数组可用滚动数组来实现。   首先澄清一下多叉树的概念:多叉树的子树是有顺序的。
  用f[n]表示有n个结点的多叉树的数目,规定f[0]=1,则,其中i表示第一棵子树中结点的数目。   首先看到题目中顶点的编号方式不利于解题,故将其转换为坐标表示法,如原来的顶点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)为顶点的三角形。下面就检查一下这些三角形是否被水平的被删边破坏掉。最后剩下的三角形就是图中存在的三角形,累计即可。   再用一遍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!,于是便看出这是一个递归过程,这也就是算法的框架。
      具体实现中有以下几个小窍门:   子集中元素之积为平方数,也就是对于任一质数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^自由变量个数。
    2style.net