神奇的分形艺术(三):Sierpinski三角形

    在所有的分形图形中,Sierpinski三角形可能是大家最熟悉的了,因为它在OI题目中经常出现,OJ上的题目省选题目中都有它的身影。这篇文章将简单介绍Sierpinski三角形的几个惊人性质。如果你以前就对Sierpinski三角形有一些了解,这篇文章带给你的震撼将更大,因为你会发现Sierpinski三角形竟然还有这些用途。

Sierpinski三角形的构造
      
    和之前介绍的两种图形一样,Sierpinski三角形也是一种分形图形,它是递归地构造的。最常见的构造方法如上图所示:把一个三角形分成四等份,挖掉中间那一份,然后继续对另外三个三角形进行这样的操作,并且无限地递归下去。每一次迭代后整个图形的面积都会减小到原来的3/4,因此最终得到的图形面积显然为0。这也就是说,Sierpinski三角形其实是一条曲线,它的Hausdorff维度介于1和2之间。

    Sierpinski三角形的另一种构造方法如下图所示。把正方形分成四等份,去掉右下角的那一份,并且对另外三个正方形递归地操作下去。挖个几次后把脑袋一歪,你就可以看到一个等腰直角的Sierpinski三角形。

      

    Sierpinski三角形有一个神奇的性质:如果某一个位置上有点(没被挖去),那么它与原三角形顶点的连线上的中点处也有点。这给出另一个诡异的Sierpinski三角形构造方法:给出三角形的三个顶点,然后从其中一个顶点出发,每次随机向任意一个顶点移动1/2的距离(走到与那个顶点的连线的中点上),并在该位置作一个标记;无限次操作后所有的标记就组成了Sierpinski三角形。下面的程序演示了这一过程,程序在fpc 2.0下通过编译。对不起用C语言的兄弟了,我不会C语言的图形操作。
{$ASSERTIONS+}

uses graph,crt;

const
   x1=320;  y1=20;
   x2=90;   y2=420;
   x3=550;  y3=420;
   density=2500;
   timestep=10;

var
   gd,gm,i,r:integer;
   x,y:real;

begin
   gd:=D8bit;
   gm:=m640x480;
   InitGraph(gd,gm,'');
   Assert(graphResult=grOk);

   x:=x1;
   y:=y1;
   for i:=1 to density do
   begin
      r:=random(3);
      if r=0 then
      begin
         x:=(x+x1)/2;
         y:=(y+y1)/2;
      end
      else if r=1 then
      begin
         x:=(x+x2)/2;
         y:=(y+y2)/2;
      end
      else begin
         x:=(x+x3)/2;
         y:=(y+y3)/2;
      end;
      PutPixel(round(x),round(y),white);
      Delay(timestep);
   end;
   CloseGraph;
end.

Sierpinski三角形与杨辉三角
    第一次发现Sierpinski三角形与杨辉三角的关系时,你会发现这玩意儿不是一般的牛。写出8行或者16行的杨辉三角,然后把杨辉三角中的奇数和偶数用不同的颜色区别开来,你会发现杨辉三角模2与Sierpinski三角形是等价的。也就是说,二项式系数(组合数)的奇偶性竟然可以表现为一个分形图形!在感到诧异的同时,冷静下来仔细想想,你会发现这并不难理解。
      
    我们下面说明,如何通过杨辉三角奇偶表的前四行推出后四行来。可以看到杨辉三角的前四行是一个二阶的Sierpinski三角形,它的第四行全是奇数。由于奇数加奇数等于偶数,那么第五行中除了首尾两项为1外其余项都是偶数。而偶数加偶数还是偶数,因此中间那一排连续的偶数不断地两两相加必然得到一个全是偶数项的“倒三角”。同时,第五行首尾的两个1将分别产生两个和杨辉三角前四行一样的二阶Sierpinski三角形。这正好组成了一个三阶的Sierpinski三角形。显然它的最末行仍然均为奇数,那么对于更大规模的杨辉三角,结论将继续成立。

Sierpinski三角形与Hanoi塔
    有没有想过,把Hanoi塔的所有状态画出来,可以转移的状态间连一条线,最后得到的是一个什么样的图形?二阶Hanoi塔反正也只有9个节点,你可以自己试着画一下。不断调整节点的位置后,得到的图形大概就像这个样子:
      
    如果把三阶的Hanoi塔表示成无向图的话,得到的结果就是三阶的Sierpinski三角形。下面的这张图说明了这一点。把二阶Hanoi塔对应的无向图复制两份放在下面,然后在不同的柱子上为每个子图的每个状态添加一个更大的盘子。新的图中原来可以互相转移的状态现在仍然可以转移,同时还出现了三个新的转移关系将三个子图连接在了一起。重新调整一下各个节点的位置,我们可以得到一个三阶的Sierpinski三角形。
  
    显然,对于更大规模的Hanoi塔问题,结论仍然成立。

Sierpinski三角形与位运算
    编程画出Sierpinski三角形比想象中的更简单。下面的两个代码(实质相同,仅语言不同)可以打印出一个Sierpinski三角形来。
const
   n=1 shl 5-1;
var
   i,j:integer;
begin
   for i:=0 to n do
   begin
      for j:=0 to n do
         if i and j = j then write('#')
         else write(' ');
      writeln;
   end;
   readln;
end.

#include <stdio.h>
int main()
{
    const int n=(1<<5)-1;
    int i,j;
    for (i=0; i<=n; i++)
    {
        for (j=0; j<=n; j++)
           printf( (i&j)==j ? "#" : " ");
        printf("n");
    }    
    getchar();
 &n
bsp;  return 0;
}

    上面两个程序是一样的。程序将输出:
#                              
##                              
# #                            
####                            
#   #                          
##  ##                          
# # # #                        
########                        
#       #                      
##      ##                      
# #     # #                    
####    ####                    
#   #   #   #                  
##  ##  ##  ##                  
# # # # # # # #                
################                
#               #              
##              ##              
# #             # #            
####            ####            
#   #           #   #          
##  ##          ##  ##          
# # # #         # # # #        
########        ########        
#       #       #       #      
##      ##      ##      ##      
# #     # #     # #     # #    
####    ####    ####    ####    
#   #   #   #   #   #   #   #  
##  ##  ##  ##  ##  ##  ##  ##  
# # # # # # # # # # # # # # # #
################################

    这个程序告诉我们:在第i行第j列上打一个点当且仅当i and j=j,这样最后得到的图形就是一个Sierpinski三角形。这是为什么呢?其实原因很简单。把i和j写成二进制(添加前导0使它们位数相同),由于j不能大于i,因此只有下面三种情况:
    情况一:
    i = 1?????
    j = 1?????
    问号部分i大于等于j
    i的问号部分记作i',j的问号部分记作j'。此时i and j=j当且仅当i' and j'=j'

    情况二:
    i = 1?????
    j = 0?????
    问号部分i大于等于j
    i的问号部分记作i',j的问号部分记作j'。此时i and j=j当且仅当i' and j'=j'

    情况三:
    i = 1?????
    j = 0?????
    问号部分i小于j
    此时i and j永远不可能等于j。i' < j'意味着i'和j'中首次出现数字不同的那一位上前者为0,后者为1,那么i和j做and运算时这一位的结果是0,与j不等。

    注意到,去掉一个二进制数最高位上的“1”,相当于从这个数中减去不超过它的最大的2的幂。观察每一种情况中i,j和i',j'的实际位置,不难发现这三种情况递归地定义出了整个Sierpinski三角形。
    嘿!发现没有,我通过Sierpinski三角形证明了这个结论:组合数C(N,K)为奇数当且仅当N and K=K。这篇文章很早之前就计划在写了,前几天有人问到这个东西,今天顺便也写进来。
    另外,把i and j=j 换成i or j=n也可以打印出Sierpinski三角形来。i and j=j表示j的二进制中有1的位置上i也有个1,那么此时i or (not j)结果一定全为1(相当于程序中的常量n),因此打印出来的结果与原来的输出正好左右镜像。

Matrix67原创
转贴请注明出处

网友Voldemort在12楼和13楼很辛苦地帖了一个杨辉三角模2问题的扩展,大家可以看看

位运算简介及实用技巧(三):进阶篇(2)

今天我们来看两个稍微复杂一点的例子。

n皇后问题位运算版
    n皇后问题是啥我就不说了吧,学编程的肯定都见过。下面的十多行代码是n皇后问题的一个高效位运算程序,看到过的人都夸它牛。初始时,upperlim:=(1 shl n)-1。主程序调用test(0,0,0)后sum的值就是n皇后总的解数。拿这个去交USACO,0.3s,暴爽。
procedure test(row,ld,rd:longint);
var
      pos,p:longint;
begin

{ 1}  if row<>upperlim then
{ 2}  begin
{ 3}     pos:=upperlim and not (row or ld or rd);
{ 4}     while pos<>0 do
{ 5}     begin
{ 6}        p:=pos and -pos;
{ 7}        pos:=pos-p;
{ 8}        test(row+p,(ld+p)shl 1,(rd+p)shr 1);
{ 9}     end;
{10}  end
{11}  else inc(sum);

end;
    乍一看似乎完全摸不着头脑,实际上整个程序是非常容易理解的。这里还是建议大家自己单步运行一探究竟,实在没研究出来再看下面的解说。

  
    和普通算法一样,这是一个递归过程,程序一行一行地寻找可以放皇后的地方。过程带三个参数,row、ld和rd,分别表示在纵列和两个对角线方向的限制条件下这一行的哪些地方不能放。我们以6×6的棋盘为例,看看程序是怎么工作的。假设现在已经递归到第四层,前三层放的子已经标在左图上了。红色、蓝色和绿色的线分别表示三个方向上有冲突的位置,位于该行上的冲突位置就用row、ld和rd中的1来表示。把它们三个并起来,得到该行所有的禁位,取反后就得到所有可以放的位置(用pos来表示)。前面说过-a相当于not a + 1,这里的代码第6行就相当于pos and (not pos + 1),其结果是取出最右边的那个1。这样,p就表示该行的某个可以放子的位置,把它从pos中移除并递归调用test过程。注意递归调用时三个参数的变化,每个参数都加上了一个禁位,但两个对角线方向的禁位对下一行的影响需要平移一位。最后,如果递归到某个时候发现row=111111了,说明六个皇后全放进去了,此时程序从第1行跳到第11行,找到的解的个数加一。

    ~~~~====~~~~=====   华丽的分割线   =====~~~~====~~~~

Gray码
    假如我有4个潜在的GF,我需要决定最终到底和谁在一起。一个简单的办法就是,依次和每个MM交往一段时间,最后选择给我带来的“满意度”最大的MM。但看了dd牛的理论后,事情开始变得复杂了:我可以选择和多个MM在一起。这样,需要考核的状态变成了2^4=16种(当然包括0000这一状态,因为我有可能是玻璃)。现在的问题就是,我应该用什么顺序来遍历这16种状态呢?
    传统的做法是,用二进制数的顺序来遍历所有可能的组合。也就是说,我需要以0000->0001->0010->0011->0100->…->1111这样的顺序对每种状态进行测试。这个顺序很不科学,很多时候状态的转移都很耗时。比如从0111到1000时我需要暂时甩掉当前所有的3个MM,然后去把第4个MM。同时改变所有MM与我的关系是一件何等巨大的工程啊。因此,我希望知道,是否有一种方法可以使得,从没有MM这一状态出发,每次只改变我和一个MM的关系(追或者甩),15次操作后恰好遍历完所有可能的组合(最终状态不一定是1111)。大家自己先试一试看行不行。
    解决这个问题的方法很巧妙。我们来说明,假如我们已经知道了n=2时的合法遍历顺序,我们如何得到n=3的遍历顺序。显然,n=2的遍历顺序如下:

00
01
11
10

    你可能已经想到了如何把上面的遍历顺序扩展到n=3的情况。n=3时一共有8种状态,其中前面4个把n=2的遍历顺序照搬下来,然后把它们对称翻折下去并在最前面加上1作为后面4个状态:

000
001
011
010  ↑
——–
110  ↓
111
101
100

    用这种方法得到的遍历顺序显然符合要求。首先,上面8个状态恰好是n=3时的所有8种组合,因为它们是在n=2的全部四种组合的基础上考虑选不选第3个元素所得到的。然后我们看到,后面一半的状态应该和前面一半一样满足“相邻状态间仅一位不同”的限制,而“镜面”处则是最前面那一位数不同。再次翻折三阶遍历顺序,我们就得到了刚才的问题的答案:

0000
0001
0011
0010
0110
0111
0101
0100
1100
1101
1111
1110
1010
1011
1001
1000

    这种遍历顺序作为一种编码方式存在,叫做Gray码(写个中文让蜘蛛来抓:格雷码)。它的应用范围很广。比如,n阶的Gray码相当于在n维立方体上的Hamilton回路,因为沿着立方体上的边走一步,n维坐标中只会有一个值改变。再比如,Gray码和Hanoi塔问题等价。Gray码改变的是第几个数,Hanoi塔就该移动哪个盘子。比如,3阶的Gray码每次改变的元素所在位置依次为1-2-1-3-1-2-1,这正好是3阶Hanoi塔每次移动盘子编号。如果我们可以快速求出Gray码的第n个数是多少,我们就可以输出任意步数后Hanoi塔的移动步骤。现在我告诉你,Gray码的第n个数(从0算起)是n xor (n shr 1),你能想出来这是为什么吗?先自己想想吧。

    下面我们把二进制数和Gray码都写在下面,可以看到左边的数异或自身右移的结果就等于右边的数。

二进制数   Gray码
   000       000
   001       001
   010       011
   011       010
   100       110
   101       111
   110       101
   111       100

    从二进制数的角度看,“镜像”位置上的数即是对原数进行not运算后的结果。比如,第3个数010和倒数第3个数101的每一位都正好相反。假设这两个数分别为x和y,那么x xor (x shr 1)和y xor (y shr 1)的结果只有一点不同:后者的首位是1,前者的首位是0。而这正好是Gray码的生成方法。这就说明了,Gray码的第n个数确实是n xor (n shr 1)。

&nbsp
;   今年四月份mashuo给我看了这道题,是二维意义上的Gray码。题目大意是说,把0到2^(n+m)-1的数写成2^n * 2^m的矩阵,使得位置相邻两数的二进制表示只有一位之差。答案其实很简单,所有数都是由m位的Gray码和n位Gray码拼接而成,需要用左移操作和or运算完成。完整的代码如下:
var
   x,y,m,n,u:longint;
begin
   readln(m,n);
   for x:=0 to 1 shl m-1 do begin
      u:=(x xor (x shr 1)) shl n; //输出数的左边是一个m位的Gray码
      for y:=0 to 1 shl n-1 do
         write(u or (y xor (y shr 1)),' '); //并上一个n位Gray码
      writeln;
   end;
end.

Matrix67原创
转贴请注明出处

Matrix67生日邀请赛 完全题解发布

题目在这里:http://www.matrix67.com/blog/article.asp?id=241

如果机房马上要关门了,或者你急着要和MM约会,请看简要题解:

1. 用类似于传统hanoi的递归方法可以做到3^n-1次。这显然是最多的了,因为总的状态数也只有3^n个。
2. 可以证明,竞赛图中不存在环当且仅当所有顶点的出度从小到大排列依次为0, 1, 2, … , n-1 。
3. 在最短路树上做树状DP,需要多叉转二叉。注意几种需要输出0的情况。
4. 搜索,算是练基本功了。用位运算优化,不加任何剪枝就能过。

否则,请慢慢阅读——

Problem 1: 为什么最少
    如果你还不熟悉Hanoi塔的解法,去题目中提到的那篇日志看看吧。如果你已经熟悉Hanoi塔的解法,你会立刻想到这道题的解法:依然是递归地解决。至于怎么递归,样例已经告诉我们了:把前n-1个金片从1号柱搬到3号柱,把第n片移到2号柱,又把那n-1片从3号柱搬回1号柱,再把第n片搬到3号柱,最后把那n-1个金片又搬过来,完成整个操作。
    我们下面解决三个问题:为什么这样不会重复出现状态,这样的移动步数是多少,为什么这样的操作步数是最多的。
    为什么这样不会出现重复的状态呢?因为我们假设前n-1个金片的移动过程中没有重复状态,而三次对n-1的调用时整个状态由于第n个金片的位置不同而不同。
    这样的方法获得的操作步数是多少呢?答案是3^n-1。我们可以用数学归纳法证明,n=1时步数为2显然正确,而f(n+1)=3f(n)+2=3*(3^n-1)+2=3^(n+1)-1。
    为什么这样的操作步数是最多的呢?废话,这样的操作步数当然是最多的,因为总的状态数也只有3^n个(每个金片的三种可能的位置确定了一种状态),你的移动步骤能比这个数目还多就见鬼了。

    这道题有人用了math库,没有提供math库导致无法编译是我的失误,向大家道歉。

    Hanoi问题的变种太多了,比如多根柱子、单向移动、双色金片等等。dd上次不是也出了一题么。

    这题代码很短,我公布在下面。
program whyleast;

procedure solve(t,a,b:integer);
begin
   if t=0 then exit else
   begin
      solve(t-1,a,b);
      writeln(a,' ',2);
      solve(t-1,b,a);
      writeln(2,' ',b);
      solve(t-1,a,b);
   end;
end;

{====main====}
var
   n,i:integer;
   ans:longint=1;
begin
   assign(input,'whyleast.in');
   reset(input);
   assign(output,'whyleast.out');
   rewrite(output);
  
   readln(n);
   for i:=1 to n do ans:=ans*3;
   writeln(ans-1);
   solve(n,1,3);
  
   close(input);
   close(output);
end.

Problem 2: 身高控制计划
    一个竞赛图是指任两个点之间都有一条有向边的图。竞赛图有很多奇妙的性质,比如一个竞赛图必然存在一条经过所有节点的路等等。
    下面我们证明,竞赛图中不存在环当且仅当所有顶点的出度从小到大排列依次为0, 1, 2, … , n-1 :
    如果一个有向图的所有点出度都至少为1,那么这个图一定有环,因为在找到环之前DFS总可以找到新的节点。如果有向图无环,必然存在一个点没有出度。由于任两点之间都有有向边,那么其它所有点都要连一条边指向它,这样其它所有点的出度都至少为1了。删掉这个出度为0的点后剩下的图仍然无环,不断对剩下的图继续上面的过程就得到了我们的结论。
    现在我们的算法就很明确了,首先统计初始状态下的出度,然后设计某种数据结构完成两种操作:改变一个数(加一减一),询问所有数是否恰好为0, 1, 2, … , n-1 。
    统计初始状态下的出度方法有很多,这里介绍两个。首先对身高排序,然后对于每个人进行二分,寻找有序数列中该数的4/5和5/4各在什么地方。还有一种方法也是先排序,然后从左到右扫描整个序列,并保持两个指针始终指向4/5和5/4处。每次开始处理一个新的数时都把两个指针适当地右移直到超出了这个数的4/5或5/4为止。两种方法都是O(nlogn)。别以为第二种方法是线性的哦,线性算法之前还有一个排序呢。
    操作的处理也有不少方法。最简单的方法就是统计当前图中出度的数目有多少种。就是说,用a[i]表示出度为i的点有多少个,然后不断更新a[i]>0的有多少个。当这个数目等于n时我们就认为图中没有环(因为出度可能的取值只有从0到n-1共n种)。
    注意,由于同一条边可能被操作多次,因此需要一个Hash表(开散列)来判重。具体地说,你需要判断这条边以前被操作过奇数次还是偶数次,以决定哪边的出度要增加,哪边的出度要减小。

Problem 3: 狼的复仇

    把这个问题中所有的最短路都画出来是什么样子?它一定是一棵树!为什么?首先,图肯定是连通的,因为源点到任一个点都有一条最短路;其次,图肯定无环,因为源点到任一个点只有一条最短路(环的出现将意味着某些点有更短的路存在)。仔细想一下Dijkstra的算法过程,不难想到Dijkstra算法的实质就是在建这棵树——每一次由x节点加上边x-y扩展到y节点就记作x是y的父亲。注意观察上图中左图是如何变成右图的。这样,题目变成了这种形式:在有根树上,除根节点之外的其它节点中选择一些节点,使得这些节点和它们所有祖先的权值和最大。这是一个经典的树型动态规划模型。我们用f[i,j]表示以节点i为根节点的子树花费j个单位的材料最多可以得到多大的攻击力。令节点1的材料和攻击力都为0,那么最后输出f[1,0..k]中的最大值即可。决策分为两类,要么该位置建一个塔,要么把所有材料适当地分给儿子(自己就不需要再建了)。但这样的复杂度太高,我们需要用DP嵌套或者更巧妙的多叉转二叉来解决。
    DP嵌套理解起来更简单,它主要是解决这样一个子问题:若某个节点有m个儿子,我们需要寻找当j1+j2+…+jm等于某个定值时f[儿子1,j1]+f[儿子2,j2]+…+f[儿子m,jm]的最大值。这个子问题与我的某次模拟赛中论文课题选择那道DP题几乎是一模一样,看一看那道题就明白了。下面简单描述多叉转二叉的方法。

    如果你还不知道多叉转二叉的话,这道题是一个绝好的学习材料。一棵多叉树可以用“左儿子右兄弟”的方法转为二叉树,具体的说就是把多叉树转化为这种形式:节点的左儿子才是真正的儿子,节点的右儿子只是和它同辈的兄弟。注意看上图的左图是如何变成右图的。现在,我们的f[i,j]表示

Matrix67生日邀请赛顺利结束 题目内容在此发布

07年5月12日晚我举办了一次OI生日邀请赛,比赛已经顺利结束。下面是这次比赛的全部试题:

题目一览

题目名称    为什么最少            身高控制计划        狼的复仇          和MM逛花园
题目类型    传统                  传统                传统              传统
源文件名称  whyleast.(pas/c/cpp)  height.(pas/c/cpp)  wolf.(pas/c/cpp)  garden.(pas/c/cpp)
输入文件名  whyleast.in           height.in           wolf.in           garden.in
输出文件名  whyleast.out          height.out          wolf.out          garden.out
时间限制    1秒                   1秒                 1秒               0.1秒
内存限制    64M                   64M                 64M               64M
测试点      10个                  10个                10个              10个
分值        100分                 100分               100分             100分

Problem 1: whyleast
为什么最少

问题描述
    时间过得真快,16号就是我的19岁生日了。为了让自己在新的一岁里人品加加,本菜鸟特地准备了原创菜题大餐供各位大牛享乐,希望大家人人400分。我们今天的第一题巨菜无比,此题乃经典的Hanoi塔问题。在1号塔上有n个盘子,你需要按照Hanoi塔的要求把所有的盘子都移动到3号塔上。
    我一直想不通的是,为什么那些智力题总是要求人们用最少的步骤完成题目中的要求。为什么非要最少呢?这次我们来点特别的,我希望你的程序能够用最多的步数达到要求,而且在此过程中不重复出现任何一种状态。

输入数据
    输入数据只有一个正整数n,表示Hanoi塔问题的金片个数。

输出数据
    第一行输出在不重复出现状态的情况下完成n阶Hanoi塔的最多步数。
    以下若干行依次表示你的操作步骤,每一行两个数a,b表示在这一步应该把a号柱最顶上的金片移动到b号柱上。
    如果有多种方案,你只需要输出其中一种即可。评测系统可以判断你的方案的正确性。

样例输入
2

样例输出
8
1 2
2 3
1 2
3 2
2 1
2 3
1 2
2 3

数据规模
    对于所有数据,n<=12。

附:Hanoi塔问题简介(摘自http://www.matrix67.com/blog/article.asp?id=29)

    法国数学家艾得渥·卢卡斯(Edouard Lucas)于1883年在一份杂志上介绍了一个引人入胜的数学谜题——汉诺塔(Tower of Hanoi),并称这与古印度的一个传说有关。显然传说的具体内容已经不在本文论述的范围内了,但我想简单的介绍一下。
    相传印度有座大寺庙,它曾被认为是宇宙的中心。神庙中放置了一块上面插有三根长木钉的木板,在其中的一根木钉上,由上至下放了64片直径由小至大的圆环形金属片。古印度教的天神指示他的僧侣们将64片金属片全部移至另一根木钉上。移动规则只有两个:
        1.在每次的移动中,只能移动一片金属片;
        2.过程中任意时刻必须保证金属片小的在上,大的在下。
    直到有那么一天,僧侣们能将64片金属片按规则从指定的木钉上全部移至另一根木钉上,那么,世界末日即随之来到,世间的一切终将被毁灭,万物都将至极乐世界。
    这个传说经常被认为是卢卡斯教授为推广这个数学谜题而捏造的,但不管怎么说,卢卡斯是成功了。这玩意儿变成了家喻户晓的益智游戏之一,后来又成为了学习递归的必修课程。

Problem 2: height
身高控制计划

问题描述
    不要总以为MM只担心自己的体重。经过Matrix67的观察,他发现他身边的MM们更关注自己的身高。MM们都希望自己能长高一些但不要长得太高。如果两个MM的身高相差不多,矮的MM会羡慕较高的MM,希望能长得和她一样修长;如果两个MM的身高相差太大,高的MM反而会想变得和较矮的MM一样娇小。Matrix67为了控制GF们的身高,采取了一项身高控制计划:任意两个女生A和B之间,假设A要比B高一些,如果A高出B的1/4,则A应该以B的身高为目标;相反,如果A的身高小于等于B的1.25倍(但仍然比B高),则B应该努力向A的身高看齐。我们假设不存在身高相等的MM。这样,Matrix67的n个MM之间产生了n(n-1)/2个单向的“榜样”关系。
    之后,Matrix67发现,这样的关系设定存在一个问题:有可能出现A以B为学习目标,B以C为学习目标,C又以A为学习目标的情况。这不相当于自己是自己的榜样么?这样的循环非常可笑,显然是不科学的。Matrix67希望调整一些关系的方向从而消除所有的循环。Matrix67每次改变其中一对MM之间的关系方向,你需要写程序判断,每一次改变后n个MM之间的“榜样”关系是否存在循环。

输入数据
    第一行输入两个用空格隔开的正整数n和m,分别表示MM的个数和改变方向的总次数。
    以下n行每行一个数,其中第i行表示编号为i的MM的身高。为了避免身高相等的情况,高度值已经被“放大”过,所有高度均为不超过2 000 000 000的正整数。
    接下来的m行里每行有用空格隔开的两个不相等的正整数A, B,表示Matrix67对编号为A的MM和编号为B的MM之间的单向关系进行反向。

输出数据
 &nbs
p;  对于Matrix67的每一次操作,你需要输出是否存在某个MM以自己为学习目标的情况(即关系图中是否存在循环)。如果是,则输出“YES”,如果不是,请输出“NO”。
    你的输出应该有m行。

样例输入
4 3
10
7
8
9
3 4
1 2
4 1

样例输出
YES
NO
YES

样例说明
        
    10超过了7的5/4,因此身高为10的MM向身高为7的MM学习;
    10小于等于8的5/4,因此身高为8的MM向身高为10的MM学习。
    同样地,还有9–>10,7–>8,9–>7,8–>9。
    这一组关系中存在多个循环,比如①–>②–>③–>①,再比如①–>②–>③–>④–>①。
    第一次Matrix67将改变③和④之间的方向,这消除了上述第二个循环,但前一个循环仍然存在。
    第二次Matrix67将改变①和②之间的方向,此时关系图中已经不存在循环了。
    第三次Matrix67改变了①和④之间的方向,这将导致新的循环①–>④–>③–>①的出现。

数据规模:
    对于30%的数据,n<=10, m<=100;
    对于50%的数据,n<=100, m<=1000;
    对于70%的数据,n<=1000, m<=100 000;
    对于100%的数据,n<=100 000, m<=100 000。

Problem 3: wolf
狼的复仇

问题描述
    山谷里有n座森林,这些森林从1到n编号。某些森林之间有小路相连,总共m条小路连通了这n座森林,任意两座森林之间都有至少一条路径可以互相到达。
    很久很久以前,这里是狼的家园。在每一座森林里都有一匹狼,每一匹狼都静静地守护着它所在的森林。直到有一天,人类出现了。它们疯狂地开垦1号森林,并且杀死了1号森林的狼。以后,这座森林就好像属于人类了一样,不时有人来到1号森林。其余的n-1匹狼不愿看到悲剧再次发生,它们希望集合它们的力量,为种族,为大自然报仇。
    机会来了。一次偶然的机会,大灰狼们获得了一个重要的情报——有一个小MM经常独自游荡于1号森林。消息传遍了整个狼群,小MM细腻的皮肤和鲜嫩的肉令它们的血液沸腾起来,每一匹狼都幻想着能扑上前去撕裂MM的身体,舔拭那温热的血液。唯一的问题是,它们需要尽快察觉小MM的出现并快速奔向目的地。但由于山谷地形复杂,在第一时间里观察到小MM的出现谈何容易。因此,狼群计划在某些森林建立瞭望塔。当小MM再次出现在1号森林里时,所在的森林里有瞭望塔的狼可以立即发现这一情况,并且沿着最短路径奔向MM。有时最短路不止一条,在途中每当有多条路可以选择时,狼总会选择前往编号较小的森林。这些狼的行动将唤起最短路上沿途经过的狼,这些最短路上的狼将会闻声而起,一同对MM发起进攻。每匹狼都有自己的攻击力,最终对小MM的攻击力即是所有到达1号森林的狼的攻击力总和。注意攻击力的值有可能为负数,因为有一些狼很可能会“拖后腿”,对整个种族的复仇计划反而不利。
    由于森林的地形不同,在不同的地方建造瞭望塔需要的材料不同。现在狼群里一共有k个单位的建筑材料,并且它们已经计算出在n-1座森林中建造瞭望塔各自需要的材料数目。请你来计算一下,在哪些森林里建造瞭望塔可以使得最终到达1号森林的狼群攻击力总和最大。当然,建筑材料不一定要全部用完,但你的方案所需要的建筑材料不能超过总的材料数k。

输入数据
    第一行输入三个用空格隔开的正整数k, n, m,分别表示材料的总数量,森林的数量和小路的数量。1号森林总是MM出没的地方,其余n-1座森林是狼所在的地方。
    第二行到第n行每行有两个用空格隔开的整数,依次表示2号森林到n号森林里的狼的攻击力和在这里建造瞭望塔所需要的材料数。狼的攻击力绝对值不超过10000(可能为负数),每个瞭望塔的材料耗费都是不超过100的正整数。
    接下来m行每行有三个用空格隔开的数x,y,d,表示在x森林和y森林之间存在一条长度为d的路。路的长度是不超过10000的正整数。

输出数据
    输出在满足材料数限制下建造瞭望塔,最多可以给MM带去多大的攻击力。

样例输入
8 7 10
1 4
1 2
2 4
-3 5
9 4
2 1
1 4 2
4 3 4
2 3 3
5 1 2
2 4 1
1 2 3
6 7 1
2 7 4
2 6 8
5 6 5

样例输出
10

样例说明
    输入数据如下图所示,我们用AP来表示攻击力,用cost来表示瞭望塔的材料花费。在涂有蓝色的节点上建造瞭望塔花费仅为7,由于7<=8,因此这种方案未超过材料预算。我们下面计算这种方案所带来的攻击力。
        
    这三匹狼的行走路线已经用箭头表示了出来。注意3号森林和7号森林的狼有多个到达节点1的最短路径,它总是选择标号较小的节点前进。这些路线经过了两个绿色的节点,这两个绿色的节点所对应的狼的攻击力也将加入总攻击力中(必须加入计算且仅算一次)。这种方案的攻击力为1+1+2+9-3=10。事实上,攻击力最大为10,没有其它的建造方案使得总攻击力大于10且材料花费不超过8。

数据规模
    对于30%的数据, k<=1, n<=10, m<=100
    对于50%的数据, k<=10, n<=100, m<=1000
    对于100%的数据,k<=100, n<=1000, m<=10000

Matrix67提醒各位女同学:独自外出时请注意安全。

Problem 4: garden
和MM逛花园

问题描述
    花园设计强调,简单就是美。Matrix67常去的花园有着非常简单的布局:花园的所有景点的位置都是“对齐”了的,这些景点可以看作是平面坐标上的格点。相邻的景点之间有小路相连,这些小路全部平行于坐标轴。景点和小路组成了一个“不完整的网格”。
    一个典型的花园布局如左图所示。花园布局在6行4列的网格上,花园的16个景点的位置用红色标注在了图中。黑色线条表示景点间的小路,其余灰色部分实际并不存在。
        

    Matrix67的生日那天,他要带着他的MM在花园里游玩。Matrix67不会带MM两次经过同一个景点,因此每个景点最多被游览一次。他和他的MM边走边聊,他们是如此的投入以致于他们从不会“主动地拐弯”。也就是说,除非前方已没有景点或是前方的景点已经访问过,否则他们会一直往前走下去。当前方景点不存在或已游览过时,Matrix67会带MM另选一个方向继续前进。由于景点个数有限,访问过的景点将越来越多,迟早会出现不能再走的情况(即四个方向上的相

原创科普说明文:递归

    我的语文暑假作业之一,要求写任一说明文。
    本人菜鸟一个,文内漏洞百出,请踊跃提出,感谢不尽。
    Matrix67原创,转帖请注明出处。

递归


    公认的递归(Recursion)的标准定义是非常难理解的:若一个对象部分地包含它自己,或用它自己给自己定义,则称这个对象是递归的;若一个过程直接地或间接地调用自己,则称这个过程是递归的过程。
    递归一词很少有过专业的定义,因此本文不在于去解释上一段文字的意义。虽然概念抽象,但递归其本身是不难理解的。通过本文的介绍,读者不一定能深入了解递归,只要能通过具体的例子模模糊糊地知道一些递归的思想和用途就可以了。
    究竟什么是递归呢?其实,递归就是大鱼吃小鱼,就是一条蛇咬住自己的尾巴。递归是指一样东西自己包含了自己。对于这一点,拿“谢尔品斯基地毯”(Sierpinski Gasket)来说明是最恰当不过的了。
    曲线在几何学中的概念很好理解,就是只有长度而没有宽度的线条。数学中有各种各样的曲线,如圆、直线、抛物线、双曲线、正弦曲线等。它们都可以用一定的方法画出来。例如,圆可以用圆规画出来,正弦曲线也可以用机器边在纸带上往复记号边拉纸带的方法画出来。事实上却没那么麻烦,画曲线有一个最常用的“万能方法”——似乎所有的曲线都可以用“描点法”画出,因为曲线没有宽度嘛,一个一个的点连起来,随便多奇怪的曲线都应该能画出。但随着数学的发展,这一点遭到了置疑。波兰数学家谢尔品斯基就想出了一个的确是一种曲线但永远无法画出的图形。他构造这种曲线的方法就运用了递归。
    随便找了一个正方形,把它分成3×3规格的相等的9个小正方形,然后把正中间的那一个挖掉。现在就只剩周围的八个小正方形了。接着重复这个过程,把8个小正方形的每一个都分成更小的9份,并挖掉它中间的那个。现在得到的就有8×8=64个正方形了。把这64个正方形继续这样划分,并且无限制的继续下去。这就是递归的思想,自己包含了自己,而后面的自己又包含了规模更小的自己。这样递归下去是没完的,因此最终得到的会是没有宽度的线条。这符合曲线的定义,但显然它是没办法画出来的。
    在现实生活中,递归的现象也是可以见到的。如果一台电视机的屏幕正显示着摄象机拍到的东西,那么把摄象机正对着这台电视机拍摄就会形成一个简单的递归。电视机显示着摄象机拍到的内容,而摄象机又对着电视机,这也就是说,摄象机将会拍摄到自己所拍到的东西。于是,递归形成了——在电视机上会显示出一层一层电视机的轮廓,即电视机里有电视机,层层循环下去永无止境。类似的例子也有一些,比如那个永远也讲不完的古老的故事,和Linda的第二张专辑的封面。
    递归通常是可以无限循环下去的。因此有这样一个笑话。作为一个狂热的电脑游戏迷,如果有一天你从一个完全陌生的地方醒来,你如何判断这是虚拟空间还是在现实中?答案是,找两面镜子来,互相对着放。如果出现周围的物体运动变慢等不正常的情况,说明你是在虚拟空间中。大自然是神奇的,它能处理两面镜子相对放置时镜子里应该显示的内容;但电脑就模拟不出来,如果电脑真遇到这种情况,指定会把CPU累死。
    但是,一旦给一个递归过程加上一个限制条件,让它递归到某一步时就停下来不要继续循环的话,递归将会派上大用场。
    我举一个最简单的例子。偶数就是能被2整除的数。我也可以用递归的方法定义偶数:一个偶数加上2还是偶数。这句话似乎足以说明了全部的数字,其实不然。因为如果没有任何限制,那么这个递归过程将是永无止境的,最终不会得到任何具体的答案。我们可以加上一个条件“0是偶数”。这样,情况就变了。比如,我们要看6是否为偶数,根据“一个偶数加上2还是偶数”,我们只需要看4是不是偶数。如果4是偶数,那么4+2也是偶数。而看4是否为偶数,又要看2是否为偶数,要看2是否为偶数,又要看0是否为偶数。本来这个递归应该是像这样无限地做下去的,但我们有了一个限制条件:我们已经知道了0是偶数。于是,2就是偶数了,4和6都是偶数了。同样的,我们就可以判断一切数字的奇偶性了。这就是利用递归来进行数学上的定义。
    这种定义方式有什么好处呢?一个简单的例子——
    很多人不明白为什么0的阶乘要规定成1,其实这用阶乘的递归定义一解释就清楚了。
    阶乘可以这样递归地定义:
        1)n的阶乘等于n-1的阶乘乘以n;
        2)1的阶乘是1;
    这样,所有自然数的阶乘就可以通过上面的两句话表示了。2的阶乘就是1×2;3的阶乘就是2的阶乘乘3,即1×2×3……不仅如此,我们还可以知道0的阶乘是多少:1的阶乘应该等于0的阶乘乘以1,显然0的阶乘必须是1才行。类似的,我们还能知道,负整数的阶乘没有意义。
    接下来,我将用两个经典的用递归的思想解决问题的例子来结束这篇文章。
    法国数学家艾得渥·卢卡斯(Edouard Lucas)于1883年在一份杂志上介绍了一个引人入胜的数学谜题——汉诺塔(Tower of Hanoi),并称这与古印度的一个传说有关。显然传说的具体内容已经不在本文论述的范围内了,但我想简单的介绍一下。
    相传印度有座大寺庙,它曾被认为是宇宙的中心。神庙中放置了一块上面插有三根长木钉的木板,在其中的一根木钉上,由上至下放了64片直径由小至大的圆环形金属片。古印度教的天神指示他的僧侣们将64片金属片全部移至另一根木钉上。移动规则只有两个:
        1.在每次的移动中,只能移动一片金属片;
        2.过程中任意时刻必须保证金属片小的在上,大的在下。
    直到有那么一天,僧侣们能将64片金属片按规则从指定的木钉上全部移至另一根木钉上,那么,世界末日即随之来到,世间的一切终将被毁灭,万物都将至极乐世界。
    这个传说经常被认为是卢卡斯教授为推广这个数学谜题而捏造的,但不管怎么说,卢卡斯是成功了。这玩意儿变成了家喻户晓的益智游戏之一,后来又成为了学习递归的必修课程。
    对汉诺塔问题的研究焦点集中在如何以最少的步骤完成全部金属片的转移这一问题上。解决这个问题的方法运用了递归的思想。
    我们可以这样想。64片金属片太多了,我们似乎能简化一下。假如我们已经知道了如何移动63片,我们就可以把这63片看成一个整体。那么这64片的移动过程就出来了:第一步,移动前63片到另一根木钉上;第二步,移动
第64片到第三根木钉上;第三步,把那63片移回第64片上面。看到了吗?问题已经解决了,因为这形成了递归。我们可以继续对移动63片的方法进行研究:把前62片移开,移动第63片,移回前62片。继续研究62片金属的移动方法……这样下去,一直推到如何移动2片金属。而移动2片金属的方法是非常简单的,已经不需要继续讨论了,于是,全部问题到此解决。
    发现递归思想的实质了吗?这让我想起了一个笑话。笑话的主人公是一个反应迟钝,只具有数学思维的数学家;为了使这个笑话更形象,我们就把这个人暂且定为童明国(注:我们数学老师的名字)。
    童明国去做消防队员。队长问:“如果你这里起火了,你怎么办?”童明国答:“用消火栓。”
    “那么如果这里没有起火呢?”
    “很简单,先把这里点燃,然后这个问题就转化为了一个我已经解决的问题了。”
    我要举的下一个例子与这个有异曲同工之处。
    小学奥赛接触过一个叫作“报30”的游戏,就是从1开始,两人轮流报数,每个人都只能报接下来的一个数或两个数。比如,第一个人可以报1,也可以报1、 2;如果第一个人报1、2,第二个人就可以报3和3、4;然后第一个人又报;这样报下去,最先报到30的人获胜。
    这个游戏非常没意思,因为它有必胜策略。
    最先报到30的人获胜,很显然,先报到27的人一定可以胜;那么,先报到24的人就一定能胜了……递归下去,21,18,最终得到的结论就是,先报到3的人一定必胜。也就是说,后报者必胜。不管先报者报多少,后报者始终报到3的倍数,这样定能获胜。
    这个游戏有很多变种,但换汤不换药,万变不离其宗。比如,把规则改成“最先报到30的人就输”。这样,先报到29的人就赢了,然后同样递归,26,23,20……
    前几天在网上看到了这个游戏的一个较难的变种。
    有10枚硬币,每人轮流取硬币,可以拿一枚、两枚或四枚;取到最后一枚硬币者胜。
    这样还有必胜策略吗?答案是肯定的,而且同样可以运用递归的思想来解决。
    如果硬币的总数只有一枚,则先取者赢;
    如果硬币的总数是两枚,则先取者赢;
    如果硬币的总数是三枚,则先取者输;
    如果硬币的总数是四枚,则先取者赢;
    如果硬币的总数是五枚,则先取者赢(取两枚,对方面临三枚的情形,必输);
    如果硬币的总数是六枚,则先取者输(不管取多少,对方面临的情形都是必胜的情形);
    如果硬币的总数是七枚,则先取者赢(取一枚,对方面临六枚的情形,必输);
    如果硬币的总数是八枚,则先取者赢(取两枚,对方面临六枚的情形,必输);
    如果硬币的总数是九枚,则先取者输(不管取多少,对方面临的情形都是必胜的情形);
    如果硬币的总数是十枚,则先取者赢(取一枚,对方面临九枚的情形,必输)。

(本文参考资料:本文  呵呵)