May 11

    早晨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的余数了。

查看更多 »

Feb 5

    MIT每年一月份都会举行一次谜题比赛(MIT Mystery Hunt),由上一年的获胜队伍来组织,比赛通常持续两天两夜。每年MIT的解谜比赛都会吸引数千人的到来,他们组成大大小小的队伍,共同参与到解谜游戏中来。游戏要求每支参赛队伍解决近百个谜题,而这些题目的答案又组成了新的谜题(Metapuzzle);所有这些谜题的答案最终会领引参赛队伍寻找到隐藏在校园中的一块硬币。这些题目不是一般的BT,很多题目连个说明都没有,你要是能独立搞出一两个来你就无敌了。
    今年的谜题已经放在了MIT的网上,大家可以到这里去看看:http://www.mit.edu/~puzzle/08/
    绝大多数题目内容都不知所云,你很可能根本看不出这道题需要你干啥;但事实上,每一道谜题都有唯一的答案。你可以点击右上角的Check Answer看到答案。

Sep 17

    今年的解谜游戏设计比赛(Puzzle Design Competition)作品目录已经出炉,我从里面挑选了一些比较有意思的玩意儿和大家分享一下。更多的作品(共55个)可以在上面的那个链接里看到。

Baby Duck Case

要求把五只鸭子全部放进盒子里。


Cheese and Mouse

把老鼠(一个圆形)和奶酪都放进盒子里。谜题的构造很容易让人产生定势思维,能想到解法(右图)的人并不多。


Cubature of the Ball

    这个谜题在一个透明的立方体里进行。立方体的内部空间分成了八等份,里面有六个可以滑动的小立方体,有一个直径稍大一点的钢珠,另外还有一块与小立方体大小相同的空白区域。钢珠是嵌在小立方体表面上的沟槽里的,游戏的目标就是把这个钢珠取出来。你可以把小立方体滑动到那个空白的空间,但每一次移动小立方体后,可供钢珠滑动的轨道都会发生变化。


Digits in a Box

把十块积木放进盒子里,然后盖上盒子的盖子。


Forest Puzzle

游戏目标:把环取下来,再上回去。


Magnetic Super Dice

    把27个小骰子拼接成一个大骰子,大骰子的每个面上必须都是9个相同的点数。有些小骰子的面上有磁铁,拼接时必须满足对接面上的磁极异性,最后得到的大骰子就是靠磁力粘起来的。


Void Cube

另类魔方,一看就明白,不用多解释了。

Aug 11

    第十九届国际信息学奥林匹克(IOI2007)将于2007年8月15-22日在克罗地亚首都萨格勒布举行,同期将举行网上同步赛。IOI2007网上同步赛为信息学爱好者们提供了一个与世界各国计算机高手交流的好机会,希望有兴趣的同学踊跃参加。
    竞赛时间:第一竞赛日8月17日、第二竞赛日8月19日,网上同步赛比正式赛晚1小时举行,当地时间为上午9:30,我国时间约在下午15:30左右(夏季两地时差为6小时)
    注:具体时间可能会有变化,请参赛的同学近期多关注本网站以及IOI竞赛网站,届时竞赛时间会在本网站发布。

IOI2007竞赛网站:http://ioi2007.hsin.hr/
IOI2007竞赛日程:http://ioi2007.hsin.hr/index.php?page=program
IOI2007网上同步赛:http://ioi2007.hsin.hr/index.php?page=online_contest
IOI2007网上同步赛注册:http://evaluator.hsin.hr/login.php?redirect=index.php


转自http://www.noi.cn/noi/showNews.jsp?newsId=100020000308
OIBH上不去了,获取新消息变得有些困难。这里转一下noi.cn上的新闻,不知道的人可以看看。
不过……估计有兴趣的人不多……

May 16

之所以没公布标程,是因为个人觉得标程写得比较丑。
既然有人需要就发布一下吧,标程丑总比没有标程好。

Problem 1
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
program height;

const
   OutputString:array[boolean]of string=('YES','NO');

type
   rec1=record
          h,p:longint;
        end;

   pointer=^rec2;
   rec2=record
          x,y:longint;
          dir:boolean;
          next:pointer;
        end;
var
   orderHeight : array[1..100000]of longint;
   SortHeight  : array[1..100000]of rec1;
   Deg,DegHash : array[0..100000]of longint;
   EdgeHash    : array[1..100000]of pointer;
   n,m,DegCount:longint;

procedure SwapRec(var t1,t2:rec1);
var
   t3:rec1;
begin
   t3:=t1;
   t1:=t2;
   t2:=t3;
end;

procedure SwapInt(var t1,t2:longint);
var
   t3:longint;
begin
   t3:=t1;
   t1:=t2;
   t2:=t3;
end;

function InsertEdgeHash(x,y:longint):integer;
var
   newp:pointer;
begin
   new(newp);
   newp^.x:=x;
   newp^.y:=y;

   if orderHeight[x] > orderHeight[y] then
      newp^.dir:=( 1.25*OrderHeight[y] <= orderHeight[x] )
   else newp^.dir:=( 1.25*OrderHeight[x] > orderHeight[y] );
   newp^.dir:=not newp^.dir;

   newp^.next:=EdgeHash[x];
   EdgeHash[x]:=newp;
   exit( ord( newp^.dir ) );
end;

function FindEdgeHash(x,y:longint):integer;
         { -1: Not Found;  0: x-->y  1: x<--y
            x Always Smaller than y }
var
   now:pointer;
begin
   now:=EdgeHash[x];
   while now<>nil do
   begin
      if now^.y=y then
      begin
         now^.dir:=not now^.dir;
         exit( ord( now^.dir ) );
      end;
      now:=now^.next;
   end;
   exit(-1);
end;

procedure UpdateDeg(t,c:longint);
begin
   if deg[t]<>c then
   begin
     dec(DegHash[Deg[t]]);
     if DegHash[Deg[t]]=0 then dec(DegCount);
     inc(DegHash[c]);
     if DegHash[c]=1 then inc(DegCount);
     Deg[t]:=c;
   end;
end;

procedure ReadHeight;
var
   i:longint;
begin
   readln(n,m);
   for i:=1 to n do
   begin
      readln(OrderHeight[i]);
      SortHeight[i].h:=OrderHeight[i];
      SortHeight[i].p:=i;
   end;
end;

procedure Sort(l,r:longint);
var
   i,j,mid:longint;
begin
   i:=l;
   j:=r;
   mid:=SortHeight[(i+j)div 2].h;
   repeat
      while SortHeight[i].h<mid do inc(i);
      while SortHeight[j].h>mid do dec(j);
      if i<=j then
      begin
         SwapRec(SortHeight[i],SortHeight[j]);
         inc(i);
         dec(j);
      end;
   until i>j;
   if l<j then Sort(l,j);
   if i<r then Sort(i,r);
end;

procedure Init;
var
   low:longint=1;
   high:longint=1;
   i:longint;
begin
   DegHash[0]:=n;
   DegCount:=1;
   for i:=1 to n do
   begin
      while SortHeight[low].h*1.25 < SortHeight[i].h do inc(low);
      while ( high<n ) and ( SortHeight[i].h*1.25 >= SortHeight[high+1].h ) do inc(high);
      UpdateDeg( SortHeight[i].p, high+low-i );
   end;
end;

procedure Solve;
var
   i,x,y:longint;
   dir:integer;
   newp:pointer;
begin
   for i:=1 to m do
   begin
      readln(x,y);
      if x>y then SwapInt(x,y);
      dir:=FindEdgeHash(x,y);
      if dir=-1 then dir:=InsertEdgeHash(x,y);
      if dir=0 then SwapInt(x,y);
      UpdateDeg(x,Deg[x]+1);
      UpdateDeg(y,Deg[y]-1);
      writeln( OutputString[DegCount=n] );
   end;
end;

{====main====}
begin
   assign(input,'height.in');
   reset(input);
   assign(output,'height.out');
   rewrite(output);

   ReadHeight;
   Sort(1,n);
   Init;
   Solve;

   close(input);
   close(output);
end.





Problem 3
program wolf;

type
   rec=record
          left,right:integer;
       end;

const
   infinite=maxlongint div 3-100000;
   //Make sure no overflows occur

var
   k,n,m  : integer;
   map    : array[1..1000,1..1000]of longint;
   dist   : array[1..1000]of longint;
   hash   : array[1..1000]of boolean;
   father : array[1..1000]of longint;

   tree : array[1..1000]of rec;
   attk : array[1..1000]of longint;
   cost : array[1..1000]of integer;
   minf : array[1..1000,0..100]of longint;

procedure readp;
var
   i,x,y,d:longint;
begin
   readln(k,n,m);
   for i:=2 to n do
      readln(attk[i],cost[i]);
   for i:=1 to m do
   begin
      readln(x,y,d);
      map[x,y]:=d;
      map[y,x]:=d;
   end;
end;

procedure init;
var
   i,j:longint;
begin
   for i:=2 to n do dist[i]:=infinite;
   for i:=2 to n do hash[i]:=false;
   dist[1]:=0;
   hash[1]:=true;

   for i:=1 to n do
   for j:=1 to n do
      if map[i,j]=0 then map[i,j]:=infinite;

   for i:=1 to n do
   for j:=1 to k do
      minf[i,j]:=-infinite;
end;

procedure sssp;
var
   i,j:longint;
   min:longint=0;
   minj:longint=1;
begin
   for i:=1 to n-1 do
   begin
      for j:=1 to n do if not hash[j] then
      begin
         if ( min+map[minj,j] = dist[j] ) and ( father[j] > minj ) then
           father[j]:=minj
         else if min+map[minj,j] < dist[j] then
         begin
           dist[j]:=min + map[minj,j];
           father[j]:=minj;
         end;
      end;

      min:=infinite;
      for j:=1 to n do if not hash[j] and (dist[j]<min) then
      begin
         minj:=j;
         min:=dist[j];
      end;

      tree[ minj ].right:=tree[ father[minj] ].left;
      tree[ father[minj] ].left:=minj;
      hash[ minj ]:=true;
   end;
end;

function solve(x,y:longint):longint;  //(node,cost)

   procedure update(var t1:longint;t2:longint);
   begin
      if t1<t2 then t1:=t2;
   end;

var
   ans:longint=-infinite;
   i,t:longint;
begin
   if minf[x,y]<>-infinite then exit(minf[x,y]);
   if y>=cost[x] then ans:=attk[x];

   if tree[x].left>0 then update(ans,solve(tree[x].left,y)+attk[x]);
  
   if tree[x].right>0 then
   begin
      update(ans,solve(tree[x].right,y));
      if y-cost[x]>0 then
         update(ans,solve(tree[x].right,y-cost[x])+attk[x]);
   end;
  
   if (tree[x].left>0) and (tree[x].right>0) then
      for i:=1 to y-1 do
         update(ans,solve(tree[x].left,i)+solve(tree[x].right,y-i)+attk[x]);

   minf[x,y]:=ans;
   exit(minf[x,y]);
end;

procedure writep;
var
   ans:longint=-infinite;
   i,j:integer;
begin
   for i:=0 to k do
     if solve(1,i)>ans then ans:=solve(1,i);
   writeln(ans);
  
   {===For Debug===
   for i:=1 to n do
   begin
      for j:=1 to k do write(minf[i,j]:3);
      writeln;
   end;
   for i:=1 to n do writeln(tree[i].left,' ',tree[i].right);
   }
end;

{====main====}
begin
   assign(input,'wolf.in');
   reset(input);
   assign(output,'wolf.out');
   rewrite(output);

   readp;
   init;
   sssp;
   writep;

   close(input);
   close(output);
end.





Problem 4
program garden;

const
   dir:array[1..4,1..2]of integer=
     ((1,0),(0,1),(-1,0),(0,-1));

type
   arr=array[1..10]of integer;
   rec=record x,y:integer;end;

var
   map:array[0..11,0..11]of boolean;
   ans:array[1..100]of rec;
   n,m,max:integer;
   step:integer=1;
   state:arr;

procedure readp;
var
   i,j:integer;
   ch:char;
begin
   readln(m,n);
   for i:=1 to n do
   begin
      for j:=1 to m do
      begin
         read(ch);
         map[i,j]:=(ch='1');
         inc(max,ord( map[i,j] ))
      end;
   readln;
   end;
end;

procedure writep;
var
   i:integer;
begin
   for i:=1 to step do
      writeln( '(' , ans[i].x , ',' , ans[i].y , ')' );
end;

procedure solve(x,y:integer);
var
   tx,ty,d:integer;
   step_cache:integer;
   state_cache:arr;
begin
   step_cache:=step;
   state_cache:=state;
   if step=max then
   begin
      writep;
      exit;
   end;

   for d:=1 to 4 do
   begin
      tx:=x+dir[d,1];
      ty:=y+dir[d,2];
      while map[tx,ty] and ( not state[tx] and(1 shl (ty-1) )>0) do
      begin
         inc(step);
         ans[step].x:=tx;
         ans[step].y:=ty;
         state[tx]:=state[tx] or ( 1 shl (ty-1) );
         tx:=tx+dir[d,1];
         ty:=ty+dir[d,2];
      end;

      tx:=tx-dir[d,1];
      ty:=ty-dir[d,2];
      if (tx<>x) or (ty<>y) then solve(tx,ty);
      state:=state_cache;
      step:=step_cache;
   end;
end;

{====main====}
var
   i,j:integer;
begin
   assign(input,'garden.in');
   reset(input);
   assign(output,'garden.out');
   rewrite(output);

   readp;
   for i:=1 to n do
   for j:=1 to m do
     if map[i,j] then
     begin
        ans[1].x:=i;
        ans[1].y:=j;
        state[i]:=1 shl (j-1);
        solve(i,j);
        state[i]:=0;
     end;

   close(input);
   close(output);
end.


依然欢迎大家来挑错

May 15

题目在这里: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]表示在这棵二叉树上的子树花费j个材料的最大值。它有这样五种决策:
    1. 自己把材料用了;
    2. 自己把材料用了后剩下的给右边;
    3. j个材料全部用到左边去,加上自己的权值;
    4. j个材料全部用到右边去,不加自己的权值;
    5. j个材料左边用一点,右边用一点,加上自己的权值。
    注意思考决策3-5中为什么有的决策要算自己的权值,有的不算自己的权值。转化后的二叉树并不是原来的树,左边和右边的意义是不同的。在原图中对比一下你就能看到这些决策的实质了。
    状态O(nk)个,决策为O(k),因此这道题可以在O(nk^2)的时间内解决。

    这题有很多细节需要注意。比如,建树时如何处理多条最短路优先选择编号较小节点,解决方法是在Dijkstra的更新过程中,当新的最短路与原来相同但新的父亲比原父亲编号小时仍然要更新。还有几种特殊情况需要输出0,比如所有的花费都大于k时,再比如所有的攻击力都小于0时。


Problem 4: 和MM逛花园
    这题搜索,DFS枚举方向,没什么好说的。在这里说一下位运算优化。
    用State[x]表示第x行的状态(0表示还没走过,1表示已经走过),用Map[x,y]表示地图第x行第y列的位置是否有景点。假如当前位置是(x,y),枚举方向d后进行下面的while循环可以飞快地确定最终状态。细心的人会发现State的储存是“反”的(和实际状态左右颠倒)。这没关系,只要State的储存一直是反的就没事了。
const
   dir:array[1..4,1..2]of integer=
     ((1,0),(0,1),(-1,0),(0,-1));

while Map[x,y] and ( not State[x] and (1 shl (y-1) )>0) do
begin
   x:=x+dir[d,1];
   y:=y+dir[d,2];
   State[x]:=State[x] or ( 1 shl (y-1) );
   ...
end;

    我的数据都是唯一解,因此你不用麻烦着写check了。


Matrix67原创
转贴请注明出处

大家帮忙校对,我先睡觉了

May 13



注:
1. 名称后带有(1)、(2)的是多次提交相同文件名,系统自动重命名的结果
2. 里面的那个“matrix67”不是我,是另外的人在捣乱(我就不说是谁了: ) )。

更多消息:http://www.oibh.org/bbs/thread-14911-1-1.html
详细的题解近期发布

May 12

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之间的单向关系进行反向。

输出数据
    对于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希望知道以这种方式游览花园是否有可能遍历所有的景点。Matrix67可以选择从任意一个景点开始游览,以任意一个景点结束。
    在上图所示的花园布局中,一种可能的游览方式如右图所示。这种浏览方式从(1,2)出发,以(2,4)结束,经过每个景点恰好一次。

输入数据
    第一行输入两个用空格隔开的正整数m和n,表示花园被布局在m行n列的网格上。
    以下m行每行n个字符,字符“0”表示该位置没有景点,字符“1”表示对应位置有景点。这些数字之间没有空格。

输出数据
    你的程序需要寻找满足“不主动拐弯”性质且遍历所有景点的游览路线。
    如果没有这样的游览路线,请输出一行“Impossible”(不带引号,注意大小写)。
    如果存在游览路线,请依次输出你的方案中访问的景点的坐标,每行输出一个。坐标的表示格式为“(x,y)”,代表第x行第y列。
    如果有多种方案,你只需要输出其中一种即可。评测系统可以判断你的方案的正确性。

样例输入
6 4
1100
1001
1111
1100
1110
1110

样例输出
(1,2)
(1,1)
(2,1)
(3,1)
(4,1)
(5,1)
(6,1)
(6,2)
(6,3)
(5,3)
(5,2)
(4,2)
(3,2)
(3,3)
(3,4)
(2,4)

数据规模
    对于30%的数据,n,m<=5;
    对于100%的数据,n,m<=10。



下面的Q&A部分是比赛中的答疑。

Q. 我的浏览器不能看到图片
A. 三道题的图片地址分别为:
http://www.matrix67.com/data/prob2.gif
http://www.matrix67.com/data/prob3.gif
http://www.matrix67.com/data/prob4.gif

Q. 第四题中,“当前方景点不存在或已游览过时,Matrix67会带MM另选一个方向继续前进”。随便选哪个方向都可以吗?
A. 选择方向时也需要保证往该方向走出的第一个位置是没被访问过的景点,因此可能有多种选择。(不知道这样表述是否足够明确了)

Q. 第三题中是否存在负权路?
A. 题目已经明确指出,路的长度是不超过10000的正整数。

Q. 提交题目时是否允许提交多余的文件?
A. 可以。评测系统会自动忽略其它文件,仅考虑四个源文件。

Q. 请保证第一题不会因输出过大而超时
A. 是的,标程在1秒时限下完全足够。如果你认为你的程序是正确的,请放心提交你的程序。

Q. 请具体说明第一题中“不重复出现状态”的含义
A. 单次操作后各个金片所在的位置被称作是一种“状态”。题目要求每一步操作后的状态不同于以往出现的任何一种状态。比如,下面的输出数据是不合法的,因为第三个操作后的状态与初始状态相同。
1 2
2 3
3 1
...
...

« 更早的日志