您的位置:首页 > 其它

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

2013-10-19 21:58 267 查看
stO 在此给某位靠打01背包处理射程并AC的大神跪了 Orz

[b]问题描述[/b][b]:[/b]

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≤10,1≤t≤250。

[b]输入格式(tank.in):[/b]

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

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

[b]输出格式(tank.out):[/b]

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

[b]输入样例[/b]

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

[b]输出样例[/b]

21

[b]分析:[/b]

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

一系列问题:

射程是整道题目的关键,可以说没有了射程问题就是一道普通的动规题目了,直接从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(残酷的现实),所以如何对射程进行巧妙的处理必不可少(这不是废话吗— —)

[b]处理射程思路:[/b]

仔细对每一次移动射程变化的观察,会发现:每移动一次,范围内就会多一组数,设决策点为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:按这个思路,一开始要对已经出现在视野内的点进行预处理~~~

[b]代码: [/b]

var  max,n,m,t,r:longint;
map,le:array[0..50,0..50] of longint;
s,temp:array[0..100] of longint;
f:array[0..50,0..50,0..250] of longint;
function min(x,y:longint):longint;
begin
if x<y then min:=x else min:=y;
end;
procedure sort(l,m:longint);
var i,j,mid,t:longint;
begin
i:=l;j:=m;mid:=temp[(l+m) div 2];
repeat
while temp[i]>mid do inc(i);
while temp[j]<mid do dec(j);
if i<=j then begin t:=temp[i];temp[i]:=temp[j];temp[j]:=t;inc(i);dec(j);end;
until i>j;
if i<m then sort(i,m);
if j>l then sort(l,j);
end;
procedure init;
var i,j,k:longint;
begin
assign(input,'tank.in');reset(input);
assign(output,'tank.out');rewrite(output);
readln(n,m,r,t);
for i:=1 to n do
begin
for j:=1 to m do read(map[i,j]);
readln;
end;
k:=0;
fillchar(temp,sizeof(temp),0);
fillchar(le,sizeof(le),0);
max:=0;
for i:=1 to r+1 do
for j:=1 to r+1 do
begin
if map[i,j]<>0 then begin inc(k);
temp[k]:=map[i,j];  end;
end;
sort(1,k);
s[0]:=0;
le[1,1]:=k;
for i:=1 to k do s[i]:=s[i-1]+temp[i];
if k>t then k:=t;
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;
end;
procedure main;
var  i,j,k,p,ir,il,jr,jl,long,ans:longint;
begin
for i:=1 to n-r do
for j:=1 to m-r do
if (i<>1) or (j<>1) then
begin
if i>1 then
begin
if i+r<=n then ir:=i+r else ir:=n;
if j+r<=m then jr:=j+r else jr:=m;
if i-r>=1 then il:=i-r else il:=1;
if j-r>=1 then jl:=j-r else jl:=1;
long:=0;
for k:=1 to jr-jl+1 do if map[ir,k+jl-1]<>0 then
begin inc(long);temp[long]:=map[ir,k+jl-1];end;
sort(1,long);
s[0]:=0;
fillchar(s,sizeof(s),0);
for k:=1 to long do s[k]:=s[k-1]+temp[k];
ans:=min(le[i-1,j]+long,t-(i+j-2));
if ans>le[i,j] then le[i,j]:=ans;
for k:=1 to ans do
begin
f[i,j,k]:=0;
for p:=0 to min(long,k) do
begin
if f[i-1,j,k-p]+s[p]>f[i,j,k] then
f[i,j,k]:=f[i-1,j,k-p]+s[p];
end;
if f[i,j,k]>max then max:=f[i,j,k];
end;
end;
if j>1 then
begin
if i+r<=n then  ir:=i+r else ir:=n;
if j+r<=m then jr:=j+r else jr:=m;
if i-r>=1 then il:=i-r else il:=1;
if j-r>=1 then jl:=j-r else jl:=1;
long:=0;
for k:=1 to ir-il+1 do if map[k+il-1,jr]<>0 then
begin inc(long);temp[long]:=map[k+il-1,jr];end;
sort(1,long);
s[0]:=0;
fillchar(s,sizeof(s),0);
for k:=1 to long do s[k]:=s[k-1]+temp[k];
ans:=min(le[i,j-1]+long,t-(i+j-2));
if ans>le[i,j] then le[i,j]:=ans;
for k:=1 to ans do
begin
for p:=0 to min(long,k) do
begin
if f[i,j-1,k-p]+s[p]>f[i,j,k] then
f[i,j,k]:=f[i,j-1,k-p]+s[p];
end;
if f[i,j,k]>max then
begin max:=f[i,j,k];end;
end;
end;
end;
writeln(max);
close(input);
close(output);
end;
begin
init;
main;
end.


tank(急速优化版)
PS:该代码无注释,但了解它的思想就看得懂了~

[b]后记:[/b]

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

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

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

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

[b]END。[/b]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: