NOIP2010提高组复赛试题

来源:互联网 发布:网络新媒介有那些 编辑:程序博客网 时间:2024/06/04 20:49

1.机器翻译 
(translate.pas/c/cpp)

【问题描述】

小晨的电脑上安装了一个机器翻译软件,他经常用这个软件来翻译英语文章。

这个翻译软件的原理很简单,它只是从头到尾,依次将每个英文单词用对应的中文含义来替换。对于每个英文单词,软件会先在内存中查找这个单词的中文含义,如果内存中有,软件就会用它进行翻译;如果内存中没有,软件就会在外存中的词典内查找,查出单词的中文含义然后翻译,并将这 个单词和译义放入内存,以备后续的查找和翻译。

假设内存中有 M 个单元,每单元能存放一个单词和译义。每当软件将一个新单词存入内存前,如果当前内存中已存入的单词数不超过 M-1,软件会将新单词存入一个未使用的内存单元;若内存中已存入 M 个单词,软件会清空最早进入内存的那个单词,腾出单元来,存放新单词。

假设一篇英语文章的长度为 N个单词。给定这篇待译文章,翻译软件需要去外存查找多少次词典?假设在翻译开始前,内存中没有任何单词。

【输入】 
输入文件名为 translate.in,输入文件共 2 行。每行中两个数之间用一个空格隔开。 
第一行为两个正整数 M和 N,代表内存容量和文章的长度。 
第二行为 N 个非负整数,按照文章的顺序,每个数(大小不超过 1000)代表一个英文单词。文章中两个单词是同一个单词,当且仅当它们对应的非负整数相同。 
 【输出】 
输出文件 translate.out 共1行,包含一个整数,为软件需要查词典的次数。

【输入输出样例 1】    

  transtate.in

 transtate.out

 3 7
 1 2 1 5 4 4 1  

 5

 【输入输出样例 1 说明】 
整个查字典过程如下:每行表示一个单词的翻译,冒号前为本次翻译后的内存状况: 
空:内存初始状态为空。 
1. 1:查找单词1 并调入内存。 
2. 1 2:查找单词 2 并调入内存。 
3. 1 2:在内存中找到单词 1。 
4.  1 2 5:查找单词 5 并调入内存。 
5.  2 5 4:查找单词 4 并调入内存替代单词 1。 
6.  2 5 4:在内存中找到单词 4。 
7.  5 4 1:查找单词 1 并调入内存替代单词 2。 
共计查了 5次词典。

【输入输出样例 2】

 translate.in

 translate.out

 2 10
 8 824 11 78 11 78 11 78 8 264  

 6

【数据范围】 
对于 10%的数据有 M=1,N≤5。 
对于 100%的数据有 0

代码

 

var m,n,i,j,k,p,ans,x:longint;

      a:array[1..100000] of longint;

 begin

   assign(input,'translate.in');

   reset(input);

   assign(output,'translate.out');

   rewrite(output);

  readln( m , n );

   p:=0;

   ans:=0;

fillchar(a,sizeof(a),0);

   for i:=1 to n do

     begin

       read(x);

       k:=0;

        for j:=1 to p do

         if a[j] = x then

           begin

            k:=j;

            break;

           end;

       if k = 0 then

         begin

          inc( p );

          if p>m then

           begin

             for j:=2 to m do a[j-1]:=a[j];

            p:=m;

           end;

          a[p]:=x;

          inc( ans );

         end;

     end;

    writeln( ans );

   close(input);

close(output);

end. 

 

2.乌龟棋 
(tortoise.pas/c/cpp)


【问题描述】 
        小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。 
        乌龟棋的棋盘是一行 N个格子,每个格子上一个分数(非负整数)。棋盘第 1 格是唯一
的起点,第 N格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。

  

乌龟棋中 M 张爬行卡片,分成 4 种不同的类型(M 张卡片中不一定包含所有 4 种类型的卡片,见样例),每种类型的卡片上分别标有 1、2、3、4 四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数。游戏中,玩家每次需要从所有的爬行卡片中选择一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到格子相应的分数。玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的分数总和。

很明显,用不同的爬行卡片使用顺序会使得最终游戏的得分不同,小明想要找到一种卡片使用顺序使得最终游戏得分最多。

现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到多少分吗? 
【输入】 
输入文件名 tortoise.in。输入文件的每行中两个数之间用一个空格隔开。 
第 1 行2 个正整数 N和 M,分别表示棋盘格子数和爬行卡片数。 
第 2 行 N个非负整数,a1, a2,  ……, aN,其中 ai 表示棋盘第 i 个格子上的分数。 
第 3 行M 个整数,b1,b2, ……, bM,表示 M 张爬行卡片上的数字。 
【输出】 
输出文件名 tortoise.out。
输出只有 1行,1 个整数,表示小明最多能得到的分数。 
 
【输入输出样例 1】 

 tortoise.in

  tortoise.out

 9 5
 6 10 14 2 8 8 18 5 17 
 1 3 1 2 1

 73
 

 
【输入输出样例 1 说明】 
小明使用爬行卡片顺序为 1,1,3,1,2,得到的分数为 6+10+14+8+18+17=73。注意,
由于起点是 1,所以自动获得第 1 格的分数 6。 
【输入输出样例 2】

 tortoise.in

 tortoise.out

 13 8
 4 96 10 64 55 13 94 53 5 24 89 8 30 
 1 1 1 1 1 2 4 1  

 455

 


【数据范围】 
对于 30%的数据有 1≤N≤30,1≤M≤12。 
对于 50%的数据有 1≤N≤120,1≤M≤50,且 4 种爬行卡片,每种卡片的张数不会超过 20。 
对于 100%的数据有 1≤N≤350,1≤M≤120,且 4 种爬行卡片,每种卡片的张数不会超过 40;0≤ai≤100,1≤i≤N;1≤bi≤4,1≤i≤M。

代码

 

program tortoise;

 var

   f:array[-1..40,-1..40,-1..40,-1..40]of longint;

  a:array[1..400]of longint;

  b:array[1..4]of longint;

  n,m,i,j,k,l,o,p:longint;

 

  function max(a,b:longint):longint;

  begin

    if(a>b)then exit(a)

        else exit(b);

  end;

 begin

   assign(input,'tortoise.in');

reset(input);

   assign(output,'tortoise.out');

rewrite(output);

  readln(n,m);

   for i:=1 to n do read(a[i]);

  for i:=1 to m do

 begin

   read(j);

   inc(b[j]);

  end;

   for i:=0 to b[1] do

   for j:=0 to b[2] do

    for k:=0 to b[3] do

      for l:=0 to b[4] do

 begin

       f[i,j,k,l]:=max(f[i-1,j,k,l],f[i,j-1,k,l]);

      f[i,j,k,l]:=max(f[i,j,k,l],f[i,j,k-1,l]);

      f[i,j,k,l]:=max(f[i,j,k,l],f[i,j,k,l-1]);

       f[i,j,k,l]:=a[i+j*2+k*3+l*4+1]+f[i,j,k,l];

     end;

   writeln(f[b[1],b[2],b[3],b[4]]);

  close(input);

close(output);

 end.  


 


3.关押罪犯 
(prison.pas/c/cpp)

【问题描述】 
        S城现有两座监狱,一共关押着 N名罪犯,编号分别为 1~N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为 c 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为 c 的冲突事件。
        每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到 S 城 Z 市长那里。公务繁忙的 Z 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。
       在详细考察了 N 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。那么,应如何分配罪犯,才能使 Z 市长看到的那个冲突事件的影响力最小?这个最小值是多少?
【输入】 
输入文件名为 prison.in。输入文件的每行中两个数之间用一个空格隔开。 
第一行为两个正整数 N和 M,分别表示罪犯的数目以及存在仇恨的罪犯对数。 
接下来的 M行每行为三个正整数 aj,bj,cj,表示 aj号和 bj号罪犯之间存在仇恨,其怨气值为 cj。数据保证1≤aj
【输出】 
输出文件prison.out 共1 行,为Z 市长看到的那个冲突事件的影响力。如果本年内监狱中未发生任何冲突事件,请输出0
【输入输出样例】

 prison.in

 prison.out

 4 6
 1 4 2534   
 2 3 3512 
 1 2 28351 
 1 3 6618 
 2 4 1805 
 3 4 12884

 3512 

   
【输入输出样例说明】 
罪犯之间的怨气值如下面左图所示,右图所示为罪犯的分配方法,市长看到的冲突事件影响力是 3512(由2 号和3 号罪犯引发)。其他任何分法都不会比这个分法更优。

 

【数据范围】 
对于 30%的数据有 N≤15。 
对于 70%的数据有 N≤2000,M≤50000。 
对于 100%的数据有 N≤20000,M≤100000。

 

代码:

type

  bian=record

    x,y,d:longint;

  end;

//==============================================================================

var

  f:Array[0..20000] of longint;

  h:array[0..20000] of longint;

  map:array[0..100000] of bian;

  n,m:longint;

//==============================================================================

procedure qsort(l,r:longint);

var

  i,j,mid:longint;

  t:bian;

begin

  i:=l;

  j:=r;

  mid:=map[(l+r) div 2].d;

  repeat

    while map[i].d>mid do inc(i);

    while map[j].d<mid do dec(j);

    if i<=j then

       begin

         t:=map[i];

         map[i]:=map[j];

         map[j]:=t;

         inc(i);

         dec(j);

       end;

  until i>j;

  if i<r then qsort(i,r);

  if l<j then qsort(l,j);

end;

//==============================================================================

procedure init;

var

  i:longint;

begin

  readln(n,m);

  fillchar(h,sizeof(h),0);

  for i:=1 to m do

    readln(map[i].x,map[i].y,map[i].d);

  for i:=1 to n do

    f[i]:=i;

  qsort(1,m);

//  for i:=1 to m do

//    writeln(map[i].d);

end;

//==============================================================================

function get(t:longint):longint;

begin

  if f[t]=t then exit(t);

  f[t]:=get(f[t]);

  exit(f[t]);

end;

//==============================================================================

procedure union(t1,t2:longint);

var

  xx,yy:longint;

begin

  xx:=get(t1);

  yy:=get(t2);

  if xx<>yy then f[xx]:=yy;

end;

//==============================================================================

procedure main;

var

  tx,ty,i,j:longint;

begin

  for i:=1 to m do

    begin

      tx:=get(map[i].x);

      ty:=get(map[i].y);

    

      if tx=ty then{如果同为一个监狱(并查集)}

         begin

           writeln(map[i].d);

           close(input);

           close(output);

           halt;

         end;

       

      if (h[map[i].x]=0) and (h[map[i].y]=0) then{如果都没有敌人就互为敌人}

         begin

           h[map[i].x]:=map[i].y;

           h[map[i].y]:=map[i].x;

           continue;

         end;

       

      if h[map[i].x]=0 then{如果x没敌人,就把y当x的敌人}

         begin

           h[map[i].x]:=map[i].y;

         end;

       

      if h[map[i].y]=0 then{如果y没敌人,就把x当y的敌人}

         begin

           h[map[i].y]:=map[i].x;

         end;

      union(h[map[i].x],map[i].y);{合并两个并查集}

      union(h[map[i].y],map[i].x);

    end;

  writeln(0);

end;

//==============================================================================

begin

  assign(input,'prison.in'); reset(input);

  assign(output,'prison.out'); rewrite(output);

  init;

  main;

  close(input); close(output);

end.


4.引水入城 
(flow.pas/c/cpp)

 
【问题描述】 

 

       在一个遥远的国度,一侧是风景秀美的湖泊,另一侧则是漫无边际的沙漠。该国的行政区划十分特殊,刚好构成一个 N行 M 列的矩形,如上图所示,其中每个格子都代表一座城市,每座城市都有一个海拔高度。
       为了使居民们都尽可能饮用到清澈的湖水,现在要在某些城市建造水利设施。水利设施有两种,分别为蓄水厂和输水站。蓄水厂的功能是利用水泵将湖泊中的水抽取到所在城市的蓄水池中。因此,只有与湖泊毗邻的第 1 行的城市可以建造蓄水厂。而输水站的功能则是通过输水管线利用高度落差,将湖水从高处向低处输送。故一座城市能建造输水站的前提,是存在比它海拔更高且拥有公共边的相邻城市,已经建有水利设施。 
       由于第 N 行的城市靠近沙漠,是该国的干旱区,所以要求其中的每座城市都建有水利设施。那么,这个要求能否满足呢?如果能,请计算最少建造几个蓄水厂;如果不能,求干旱区中不可能建有水利设施的城市数目。 
【输入】 
输入文件名为 flow.in。输入文件的每行中两个数之间用一个空格隔开。 
输入的第一行是两个正整数 N和 M,表示矩形的规模。 
接下来 N行,每行 M 个正整数,依次代表每座城市的海拔高度。 
【输出】 
输出文件名为 flow.out。 
输出有两行。如果能满足要求,输出的第一行是整数 1,第二行是一个整数,代表最少建造几个蓄水厂;如果不能满足要求,输出的第一行是整数 0,第二行是一个整数,代表有几座干旱区中的城市不可能建有水利设施。 
【输入输出样例 1】

 flow.in

flow.out

 2 5
 9 1 5 4 3  1 
 8 7 6 1 2  

 1

  
【样例 1 说明】 
只需要在海拔为 9 的那座城市中建造蓄水厂,即可满足要求。 
【输入输出样例 2】   

 flow.in

flow.out

 3 6   
 8 4 5 6 4 4  
 7 3 4 3 3 3   
 3 2 2 1 1 2

 1
 3

【样例 2 说明】 

 

       上图中,在 3 个粗线框出的城市中建造蓄水厂,可以满足要求。以这 3个蓄水厂为源头在干旱区中建造的输水站分别用3种颜色标出。当然,建造方法可能不唯一。

代码

 

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

var

col,a:array[0..501,0..501] of longint;

f:array[0..500] of longint;

c:array[0..500,1..2] of longint;

d:array[0..500000] of record x,y:longint;end;

n,m,i,j,color:longint;

function min(a,b:longint):longint; begin if a<b then exit(a) else exit(b); end;

procedure Judge;

var i,j:longint;

        procedure bfs(x,y:integer);

        var i,t,f:longint;

        begin

        t:=1;f:=1;d[t].x:=x;d[t].y:=y;col[x,y]:=color;

        repeat

          for i:=1 to 4 do

              if col[d[f].x+h[i,1],d[f].y+h[i,2]]=0 then

              if a[d[f].x+h[i,1],d[f].y+h[i,2]]<a[d[f].x,d[f].y] then

              begin

                inc(t);

                d[t].x:=d[f].x+h[i,1]; d[t].y:=d[f].y+h[i,2];

                col[d[t].x,d[t].y]:=color;

              end;

          inc(f);

          until f>t;

          end;

begin

  for i:=0 to n+1 do begin a[i,0]:=maxlongint; a[i,m+1]:=maxlongint; end;

  for i:=0 to m+1 do begin a[0,i]:=maxlongint; a[n+1,i]:=maxlongint; end;

  for color:=1 to m do bfs(1,color);

  j:=0;

  for i:=1 to m do

     if col[n,i]=0 then inc(j);

  if j>0 then begin writeln(0); writeln(j); close(input);close(output);halt; end;

end;

procedure floodfill;

        procedure bfs(x,y:integer);

        var i,t,f:longint;

        begin

        t:=1;f:=1;d[t].x:=x;d[t].y:=y;col[x,y]:=color;

        repeat

          for i:=1 to 4 do

              if col[d[f].x+h[i,1],d[f].y+h[i,2]]=0 then

              if a[d[f].x+h[i,1],d[f].y+h[i,2]]>a[d[f].x,d[f].y] then

              begin

                inc(t);

                d[t].x:=d[f].x+h[i,1]; d[t].y:=d[f].y+h[i,2];

                col[d[t].x,d[t].y]:=color;

              end;

          inc(f);

          until f>t;

        end;

var i,j:longint;

begin

  for i:=0 to n+1 do begin a[i,0]:=0; a[i,m+1]:=0; end;

  for i:=0 to m+1 do begin a[0,i]:=0; a[n+1,i]:=0; end;

  fillchar(col,sizeof(col),0);

  for color:=1 to m do if col[n,color]=0 then bfs(n,color);

  for i:=1 to m do c[i,1]:=col[1,i];

  fillchar(col,sizeof(col),0);

  for color:=m downto 1 do if col[n,color]=0 then bfs(n,color);

  for i:=1 to m do c[i,2]:=col[1,i];

end;

 

procedure DP;

var i,j:longint;

begin

f[0]:=0;

for i:=1 to m do

begin

 f[i]:=maxint;

 for j:=1 to m do

  if (c[j,2]>=i)and(c[j,1]<=i) then

 f[i]:=min(f[i],f[c[j,1]-1]+1);

end;

writeln(1);

writeln(f[m]);

end;

BEGIN

assign(input,'flow.in');reset(input);

   assign(output,'flow.out');rewrite(output);

read(n,m);

for i:=1 to n do

 for j:=1 to m do read(a[i,j]);

judge;

floodfill;

DP;

close(input);close(output);

END.

 

NOIP2010解题报告

首先对这次比赛做一个总体分析。第一题我觉得没什么好说的,作为一道简单题还是挺简单的。第二题是动态规划,朴素的做法可以得到30-50分,如果发现了方程中的一个小优化并且稳稳当当地作对,可以拿到满分。第三题暴力搜索可以拿到30分。如果想得到满分,首先要想到二分答案,可能多数同学没有想到这个方向上,再验证是否是二分图,通过BFS实现,就可以拿到满分了。第三题还有一个并查集的方法也可以做。第四题确实较难,首先通过BFS判断“无解”的情况,可以拿到30分,而对于有解的情况,需要仔细分析,得出一个“重要结论”,之后用到BFS和Dp可以拿到满分。
下面我将逐题进行分析
第一题:
(由于是NOIP,这道题的题解我就给个时间复杂度高一点但是好理解的吧,大牛们忽略此题题解)
我们用一个a数组维护当前内存存储的单词,并且从1到m的位置就是单词进入内存的先后顺序。一开始内存是空的,所以所有位置上都是-1。
我们顺序操作文章中的n个单词,对于每个单词,我们都查找它当前是否在内存中。若它在内存中,直接忽略过去;若它不在内存中,就需要把内存的2~m位置上的单词往前挪(挪到1~m-1处),并把新的这个单词放到m处,并把答案类加1。最后输出答案即可。
第二题:
我们的任务是用给定的卡片从1走到n,最终得分最大。动态规划的味道似乎挺浓的,于是尝试一下。
动态规划首先要用变量来表示出状态。若要表示当前状态,需要用的变量是:当前位置、当前还剩余哪些卡片。当前位置很好记录,一个变量i即可。而“当前还剩余哪些卡片”似乎并不好记录。但仔细观察数据规模就会发现,卡片上的数值只会是1、2、3、4,并且每种不会超过40张,这样我们可以用4个变量j1、j2、j3、j4也可以记录了。而我们仔细观察就会发现,这5个变量存在一个恒等式:i+j1+j2*2+j3*3+j4*4=n,也就是说其中一个可以通过另外四个推出来。于是我们只需要4个变量(j1, j2, j3, j4)就可以表示出当前的状态了。我们另F[j1, j2, j3, j4]表示剩余j1张1卡片、剩余j2张2卡片、剩余j3张3卡片,剩余j4张4卡片,走到n的最大得分。
而转移成子问题似乎也很显然:
F[j1, j2, j3, j4] = a[n - j1+j2*2+j3*3+j4*4] + max(F[j1-1, j2, j3, j4]
                                                    F[j1, j2-1, j3, j4]
                                                    F[j1, j2, j3-1, j4]
                                                    F[j1, j2, j3, j4-1])
边界F[0, 0, 0, 0]=a[n]。所求为F[m1, m2, m3, m4]。
时间复杂度:O(p^4)   其中p为题目给定的每种卡片不超过40
第三题:
这道题是一个明显的“最大值最小”问题。解释一下,就是让你找出一个分配方案,使得所有冲突中的最大值尽量的小。“最大值最小”问题99%都可以用二分答案来做。再具体解释一下:

 

 

 

如果最后最小的最大冲突为ans。对于一个在区间[0, ans)的数k,一定不满足命题“所有冲突值都小于等于k”,而对于一个在区间[ans, 1000000000]的数k,一定满足命题“所有冲突值都小于等于k”。这就好比是一个猜价格的游戏,你猜一个价格,我告诉你高了还是低了,如果高了你就往低猜,如果低了你就往高猜。而这道题是你猜一个k,根据它满足命题还是不满足命题决定再大点还是再小点。当然,最快的方法就是在区间[0, 1000000000]上二分这个k。
那接下来我们的任务就变成了,验证一个k,是否能满足命题“所有冲突都小于等于k”。而这个命题等价于“所有冲突值大于k的两个犯人必须被关押在不同的监狱”。那么通过BFS染色就可以判断。
时间复杂度:O(M*log(10^9))
对于另一做法,需要一个更高级的知识——并查集。这里简单说说。
首先把所有冲突按照从大到小进行排序,然后进行操作。我们都检验,对于当前冲突的两个人是否能把他俩分开。直到遇到两个人,他俩不可能被分开(如果分开了就跟前面冲突了),那他俩的冲突度就是最后的答案了。而利用并查集可以维护已知的一些人的关系。
时间复杂度:O(MlogM)


第四题:
这道题个人认为出的不错。
首先判断是否有解。判断方法很简单:假设靠水库的所有城市((1, 1)~(1, m))都建造水泵,判断是否能覆盖所有靠沙漠的城市。如果无解就顺便输出不能覆盖的个数。这些都能通过一次BFS实现,时间复杂度O(N*M)。
接下来考虑有解的情况。我就顺着我考场上的思路说吧。
我首先想到,能否通过一个预处理,求出:(1)对于单独在每个靠水库的城市(1, i)修建水泵,求出它能覆盖到的靠沙漠的城市的**Ai。然后再考虑:(2)通过最少个数的Ai,使得它们的并集覆盖1~n。
面对这两个问题,我同时进行思考,首先第一个问题N^3可以解决,N^2似乎没有更好方法,但也勉强接受,毕竟够90分了。面对第二个问题,尝试往网络流方向想,试图构建最小割模型,可似乎没什么进展。
此时我仔细看了下第二个问题,发现它是NP问题!道理很简单,我们把每个靠水库的城市当做一个Boolean型变量Ri,建就是TRUE,不建就是FALSE。把每个靠沙漠的城市当做一个表达式,如果这个城市出现在某几个水库的**中,例如Ai、Aj、Ak、Ap、Aq中,那么这个表达式就是“Ri Or Rj Or Rk Or Rp Or Rq”,我们的目的是满足所有表达式。显然这是一个多sat问题,而多sat问题是NP问题。(这段完全是思路,看不懂的就忽略过去)
从而我们得出一个结论:这个图、这个问题肯定有它的特殊性!
究竟是什么特殊性呢?经过我的观察,我大胆预测一个结论:如果某个靠水库的城市构建水泵,它能覆盖的Ai一定是连续的一段!(如果不是连续一段必然无解)道理很简单:
|<--------O--->|
             |
             |
------>X<-------
例如,出现这样一个靠水库的城市“O”,它覆盖的不是连续一段区间,即中间有一个或多个“X”。由于“O”到“X”的两条路海拔高度都是严格递降的(如箭头方向)。也就是说,在这个“环”上无法找到一个点到“X”的路径为单调递降的。那“X”位置就不可能被任意一个靠水库的城市覆盖。
所以我们得出重要结论:在每一个靠水库的城市修建水泵,能覆盖的区间一定是连续的一段!
那接下来问题有两个:(1)对于每个靠水库的城市i,求出它能覆盖的靠沙漠的城市ST[i]和ED[i]。(2)已知m条线段,每条线段是从ST[i]到ED[i],求最少的线段能覆盖1-n。
先说第二个问题,是一个经典的Dp。(不怕大牛笑话我们就说个暴力点但是易懂的方法吧)
我们定义F[i]表示覆盖1~i需要的最小线段数量。
F[i] = min(F[ST[j]-1])+1    1<=j<=m 且必须满足ST[j] <= i <= ED[j]
边界F[0] = 0,所求为F[m]。
时间复杂度O(M^2)
而对于第一个问题,O(N*M*M)的方法很简单,分别从(n, 1)~(n, m)搜索m次即可。我们重点说说O(N*M)的方法。由于ST和ED是对称问题,我们只说ST如何求。我们先从(n, 1)开始往上面BFS(只不过改成从低向高搜索),能够搜到的点就是“能够从这个点传递到(n, 1)的点”。显然这些点能够到达的底层最左端的点就是1。然后我们再从(n, 2)往上BFS,只不过对于(n, 1)之前搜到过的点就不必再重新搜索了(因为这次搜到的点结果是2,肯定比之前的1大,而我们求的是最小值)。接下来我们再从(n, 3)、(n, 4)……往后搜索,同理遇到之前搜过的点就不必再重新搜索了。由于每个点只被搜到一次,所以复杂度是O(N*M)。而对于ED,只需要从(n, m)倒叙至(n, 1)的顺序搜索即可。
最终这道题可以在O(M*(M+N))的时间复杂度解决。

0 0
原创粉丝点击