第二题:坦克游戏1.0(方法:动态规划)

                                    stO        在此给某位靠打01背包处理射程并AC的大神跪了     Orz


   问题描述      

         henry公司最近推出了一款新的坦克游戏。在游戏中,你将操纵一辆坦克,在一个N×M的区域中完成一项任务。在此的区域中,

   将会有许多可攻击的目标,而你每摧毁这样的一个目标,就将获得与目标价值相等的分数。只有获得了最高的分数,任务才算完成。

   同时,为了增加游戏的真实性和难度,该游戏还做了以下的限制:

 

         (1)坦克有射程r的限制。为方便计算,射程r规定为:

                   若坦克位于(x,y)格,则它可攻击的目标(x1,y1)必须满足|x-x1|,|y-y1|∈[0,r]。

         (2)对坦克完成任务的时间有严格限制,规定为t秒。其中,坦克每进行一次移动都需1秒的时间,每攻击一个目标也需1秒的时

         间。时间一到t秒,便对此次任务进行记分。

         (3)坦克最初位于左上角,且移动方向只准是向右或向下,每次只允许移动一格。

         在以上的限制条件下,要完成该任务便成为了一件很难事情。因此,你必须为此编写一个程序,让它助你完成这个艰巨的任务。

 

         数据范围1≤N、M≤50,1≤r≤101≤t≤250。


 

   输入格式(tank.in):

        第一行四个整数N、M、r、t,分别表示区域的长、宽,以及射程和完成任务时间。

        接下来N行是一个N×M的矩阵,对应每个位置上目标的价值。


 

   输出格式(tank.out):

        输出文件仅一个数max,即该任务中可得到的最高分数。


 

   输入样例

       5 5 2 7

       0 5 0 0 4

       0 0 0 0 2

       0 0 0 0 0

       0 0 0 0 0

       5 0 3 0 11


 

   输出样例

        21


 

    分析:

          首先,文中提到的:每次只能向右,向下移动或开炮,所以基本上第一印象就是动规,所以为了完成动规就会想到下面的

    一系列问题:

          

               射程是整道题目的关键,可以说没有了射程问题就是一道普通的动规题目了,直接从1 到 n,1 到 m 循环一遍就有结果

         了,射程是一个(r+1)*(r+1)  的正方形,如果每次打掉价值最高的,那么就可以完成对射程的处理,但于是又引出了下

         面的问题:

               【1】:因为对于一个目标,是只可以打一次的,也就是说这次打过,下次就得换一个了,所以不同时间同一方块能打到

                的最大价值不是固定的,要专门开一个数组来记录并不断更新,这就是代码的难度直线上升。

               【2】:其次,对于一个决策点x,y,记上一秒x,y-1能达到的最大价值为val1,再记上一秒x,y 能达到的最大价值为

                val2,则这一秒在点x,y的价值可以是val1,也可以是 (val2+(此时x,y点能打到最大的价值)), 如果后者大,那么

                这么做的话,势必会选后者添加进该点,但是:设此时x,y能打到的第二大的价值为 t,那么在下一秒,同样决策点是

                x, y,由于val2已经打过最大的,所以这一秒val2只能打掉次大的,所以这时记录的最大价值为val2+最大的价值+加

                次大的价值, 但如果上一秒选的是val1,这一秒它能达到的价值就是val1+最大的价值,那么就会存在一种情况:

                           虽然val1<val2+最大价值,但val1>val2+次大价值,那么就会有val1+最大价值>val2+最大价值+次大价值!

                      也就是说,对于这个决策点x,y,上一秒的决策是最优,但下一秒却不是最优值。这样问题就蛋疼了— —

          想到这,就应该要做出选择了:

                【1】:放弃动态规划,选择和搜索探讨一下感情 — — 不要在一棵树上吊死。。

                【2】:霸王硬上弓— — 但就必须对射程进行一个可行的判定。直接全范围搜显然是不行的了— —

          但强搜显然是会超时的Orz(残酷的现实),所以如何对射程进行巧妙的处理必不可少(这不是废话吗— —)


    处理射程思路:

           仔细对每一次移动射程变化的观察,会发现:每移动一次,范围内就会多一组数,设决策点为x,y,若x,y由x-1,y 向下移

     动得到:即为从( x+r,y-r 到 y+r)的2r个数。这是一个可喜的发现,因为严格的讲起来,如果对于一个出现在视野内的点,马上

     把他打掉和之后在把它打掉是没有差别的,所以对于这组出现在视野内的数,现在就打掉和之后打掉是没差的,为了方便编程,完

     全可以设定为:要吗就一进视野就马上打掉,要吗就一直都不打。

           所以每次要考虑的数就被固定了,而且相邻两点要考虑的不会有重合(这是非常好的,就免去的上面分析中提到的为了判重而

     不断更新的问题),并且上面分析的前一秒最忧后一秒不一定最优的问题就不存在了。因为做的时候采用一次性添加进要打掉的点

     ,也就是说点是同时添加的(影响不大,没考虑到也没事,只是提到可能会有这种情况)。 

           最后就是要对某个点,打k 个目标的得分进行预处理:

                 记le  [ i , j , k ]为从左边到达( i ,j )这个点,打掉此时k 个新出现在视野中的目标能获得的得分;

                 记up[ i , j , k  ]为从上面到达( i , j)这个点,打掉此时k个新出现在视野中的目标能获得的得分;

      PS:按这个思路,一开始要对已经出现在视野内的点进行预处理~~~


    代码:         

 1 var
 2 res:array[-20..251,-100..100,-100..100] of longint;//res是进行动规的数组,
 3                                                    //res[该时间,横坐标,纵坐标]为该时这点得分最大值
 4 val:array[-20..100,-100..100] of longint;          //记录得分点
 5 up,le:array[0..51,0..51,0..100] of longint;        //up,le均为
 6 n,m,r,t:longint;
 7 
 8 Function max(a,b:longint):longint;                 //求两者最大值得函数,返还值为较大者
 9 begin if a>b then exit(a) else exit(b); end;
10 
11 Procedure init;
12 var d:array[0..200] of longint;
13     i,j,l:longint;
14 begin
15   l:=0;
16   readln(n,m,r,t);                     //读入行、列、射程、限时
17   fillchar(res,sizeof(res),$ff);       //初始化动规数组,赋值为-1是为了防止某些不可能情况成立
18   for i:=1   to n   do
19   for j:=1   to m   do read(val[i,j]); //读入目标价值
20   for i:=1   to r+1 do                 //因为按照思路是考虑视野的边界,所以一开始要处理视野内的点
21   for j:=1   to r+1 do
22   if  val[i,j]>0  then begin           //d数组存放该视野中所有的有分的点的分
23    inc(l);
24    d[l]:=val[i,j];
25   end;
26   for i:=1   to l-1 do                 //对这个数组进行排序,因为分高的优先去
27   for j:=i+1 to l   do 
28   if  d[i] < d[j] then begin
29    d[0]:=d[i]; d[i]:=d[j]; d[j]:=d[0];
30   end;
31   for i :=2  to l do inc(d[i],d[i-1]); //做完这步d[x]表示的就是:打掉x 个点的最大得分
32   for i :=1  to l do res[i,1,1]:=d[i]; //初始化站在原地不动一直打的情况
33   close(input);
34 end;
35 
36 Procedure fill;                        //这个过程就是对每个点的新增视野做类似上方的步骤,即预处理
37 var d:array[0..200] of longint; 
38     i,j,k,l,p:longint;                 //l 存的是新增视野内有得分点的数量
39 begin
40   res[0,1,1]:=0;
41   for i:=1 to n do 
42   for j:=1 to m do begin               //枚举每个点  
43     //下面这部分是从上方移动到该点的情况
44     l:=0;                              //清空总数
45     for k:=j-r to j+r do               //查找新增视野中有得分得点
46     if val[i+r,k]>0 then begin         //添加进数组,总数+1
47       inc(l);
48       d[l]:=val[i+r,k];
49     end;
50     for k:=1   to l-1 do               //同样排序
51     for p:=k+1 to l   do
52     if  d[k] < d[p] then begin
53       d[0]:=d[k]; d[k]:=d[p]; d[p]:=d[0];
54     end;
55     for k:=1   to l   do up[i,j,k]:=up[i,j,k-1]+d[k]; //将得到的数据存入up数组中
56     up[i,j,0]:=l;                                     //储存最多可以打多少个
57     //这部分是从左方移动到该点的情况
58     l:=0;                              //清空总数
59     for k:=i-r to i+r do               //查找新增视野中有得分得点
60     if val[k,j+r]>0 then begin         //添加进数组,总数+1
61       inc(l);
62       d[l]:=val[k,j+r];
63     end;
64     for k:=1   to l-1 do               //同样排序
65     for p:=k+1 to l   do
66     if  d[k] < d[p] then begin
67       d[0]:=d[k]; d[k]:=d[p]; d[p]:=d[0];
68     end;
69     for k:=1   to l   do le[i,j,k]:=le[i,j,k-1]+d[k]; //将得到的数据存入up数组中
70     le[i,j,0]:=l;                                     //储存最多可以打多少个
71   end;
72 end;
73 
74 Procedure main;
75 var i,j,z,k:longint;
76 begin
77   for z:=1 to t do                 //把时间做状态
78   for i:=1 to n do                 //枚举每个点
79   for j:=1 to m do
80   if i+j-2<=z then begin           //边界,因为第z 时,走到的点 i,j与1,1的距离不能超过 z
81     res[z,i,j]:=max(res[z-1,i,j],max(res[z-1,i,j-1],res[z-1,i-1,j]));  //直接移动来,不管新增目标
82     for k:=1 to up[i,j,0] do if (z-1-k>0)and(res[z-1-k,i-1,j]>=0) then 
83     res[z,i,j]:=max(res[z,i,j],res[z-1-k,i-1,j]+up[i,j,k]);      //枚举打多少个目标(从上面来这点的)
84     for k:=1 to le[i,j,0] do if (z-1-k>0)and(res[z-1-k,i,j-1]>=0) then 
85     res[z,i,j]:=max(res[z,i,j],res[z-1-k,i,j-1]+le[i,j,k]);      //枚举打多少个目标(从上面左边来的)
86   end else break;
87   k:=0;
88   for i:=1 to n do                 //循环一边寻找最大值
89   for j:=1 to m do
90   if  res[t,i,j]>k then k:=res[t,i,j];
91   writeln(k);                      //输出结果
92 end;
93 
94 begin
95   assign(input,'tank.in');  assign(output,'tank.out');
96   reset(input);             rewrite(output);
97   init;                     fill;
98   main;                     close(output);
99 end.
tank(动规打法)

     优化:

           虽然根据测试时给的数据都AC的,但总耗时还是达到了1.4秒多,在完成编程后就可以想想优化:

                      在预处理up和le数组的时候,由于每次新出现在视野内的目标不多,所以一般采用选择排序,但执行次数一多时间就

                上去了— —。所以采用快排的打法并进行一定的修改就可以达到0.30秒AC全数据~,这边贴一个标程:

  1 var  max,n,m,t,r:longint;
  2      map,le:array[0..50,0..50] of longint;
  3      s,temp:array[0..100] of longint;
  4      f:array[0..50,0..50,0..250] of longint;
  5  function min(x,y:longint):longint;
  6   begin
  7    if x<y then min:=x else min:=y;
  8   end;
  9  procedure sort(l,m:longint);
 10   var i,j,mid,t:longint;
 11    begin
 12      i:=l;j:=m;mid:=temp[(l+m) div 2];
 13      repeat
 14       while temp[i]>mid do inc(i);
 15       while temp[j]<mid do dec(j);
 16       if i<=j then begin t:=temp[i];temp[i]:=temp[j];temp[j]:=t;inc(i);dec(j);end;
 17      until i>j;
 18     if i<m then sort(i,m);
 19     if j>l then sort(l,j);
 20    end;
 21  procedure init;
 22   var i,j,k:longint;
 23    begin
 24      assign(input,'tank.in');reset(input);
 25      assign(output,'tank.out');rewrite(output);
 26      readln(n,m,r,t);
 27      for i:=1 to n do
 28       begin
 29        for j:=1 to m do read(map[i,j]);
 30        readln;
 31       end;
 32     k:=0;
 33     fillchar(temp,sizeof(temp),0);
 34     fillchar(le,sizeof(le),0);
 35     max:=0;
 36     for i:=1 to r+1 do
 37      for j:=1 to r+1 do
 38       begin
 39         if map[i,j]<>0 then begin inc(k);
 40         temp[k]:=map[i,j];  end;
 41       end;
 42     sort(1,k);
 43     s[0]:=0;
 44     le[1,1]:=k;
 45     for i:=1 to k do s[i]:=s[i-1]+temp[i];
 46     if k>t then k:=t;
 47     for i:=1 to k do begin f[1,1,i]:=s[i];if f[1,1,i]>max then max:=f[1,1,i];end;
 48    end;
 49  procedure main;
 50   var  i,j,k,p,ir,il,jr,jl,long,ans:longint;
 51    begin
 52      for i:=1 to n-r do
 53       for j:=1 to m-r do
 54        if (i<>1) or (j<>1) then
 55         begin
 56           if i>1 then
 57            begin
 58              if i+r<=n then ir:=i+r else ir:=n;
 59              if j+r<=m then jr:=j+r else jr:=m;
 60              if i-r>=1 then il:=i-r else il:=1;
 61              if j-r>=1 then jl:=j-r else jl:=1;
 62              long:=0;
 63              for k:=1 to jr-jl+1 do if map[ir,k+jl-1]<>0 then
 64               begin inc(long);temp[long]:=map[ir,k+jl-1];end;
 65              sort(1,long);
 66              s[0]:=0;
 67              fillchar(s,sizeof(s),0);
 68              for k:=1 to long do s[k]:=s[k-1]+temp[k];
 69              ans:=min(le[i-1,j]+long,t-(i+j-2));
 70              if ans>le[i,j] then le[i,j]:=ans;
 71              for k:=1 to ans do
 72               begin
 73                 f[i,j,k]:=0;
 74                 for p:=0 to min(long,k) do
 75                  begin
 76                    if f[i-1,j,k-p]+s[p]>f[i,j,k] then
 77                     f[i,j,k]:=f[i-1,j,k-p]+s[p];
 78                  end;
 79                 if f[i,j,k]>max then max:=f[i,j,k];
 80               end;
 81            end;
 82          if j>1 then
 83            begin
 84              if i+r<=n then  ir:=i+r else ir:=n;
 85              if j+r<=m then jr:=j+r else jr:=m;
 86              if i-r>=1 then il:=i-r else il:=1;
 87              if j-r>=1 then jl:=j-r else jl:=1;
 88              long:=0;
 89              for k:=1 to ir-il+1 do if map[k+il-1,jr]<>0 then
 90               begin inc(long);temp[long]:=map[k+il-1,jr];end;
 91              sort(1,long);
 92              s[0]:=0;
 93              fillchar(s,sizeof(s),0);
 94              for k:=1 to long do s[k]:=s[k-1]+temp[k];
 95              ans:=min(le[i,j-1]+long,t-(i+j-2));
 96              if ans>le[i,j] then le[i,j]:=ans;
 97              for k:=1 to ans do
 98               begin
 99                 for p:=0 to min(long,k) do
100                  begin
101                    if f[i,j-1,k-p]+s[p]>f[i,j,k] then
102                     f[i,j,k]:=f[i,j-1,k-p]+s[p];
103                  end;
104                 if f[i,j,k]>max then
105                  begin max:=f[i,j,k];end;
106               end;
107            end;
108         end;
109       writeln(max);
110       close(input);
111       close(output);
112    end;
113  begin
114    init;
115    main;
116  end.
tank(急速优化版)

           PS:该代码无注释,但了解它的思想就看得懂了~


     后记:

           一、如文章第一句 — — 用01背包处理新出现在视野中的点也是可以A的— —只比优化慢0.2秒。还可以用贪心— —。

                 反正解决了对视野的处理,将其转化为新视野后就八仙过海,各显神通啦— — 所以不要拘泥于一种算法— —,还是

                 那句话:不要在一棵树上吊死— —

           二、总的来说,无论是对视野的处理还是解决视野问题后的预处理都让我收获了很多,也意识到了自己的很多不足Orz。。


                                                                                                                            END。

 

原文地址:https://www.cnblogs.com/qq359084415/p/3378274.html