【动态规划】猫狗大战(背包)

第二题

描述 Description

新一年度的猫狗大战通过SC(星际争霸)这款经典的游戏来较量,野猫和飞狗这对冤家为此已经准备好久了,为了使战争更有难度和戏剧性,双方约定只能选择Terran(人族)并且只能造机枪兵。

比赛开始了,很快,野猫已经攒足几队机枪兵,试探性的发动进攻;然而,飞狗的机枪兵个数也已经不少了。野猫和飞狗的兵在飞狗的家门口相遇了,于是,便有一场腥风血雨和阵阵惨叫声。由于是在飞狗的家门口,飞狗的兵补给会很快,野猫看敌不过,决定撤退。这时飞狗的兵力也不足够多,所以没追出来。

由于不允许造医生,机枪兵没办法补血。受伤的兵只好忍了。555-

现在,野猫又攒足了足够的兵力,决定发起第二次进攻。为了使这次进攻给狗狗造成更大的打击,野猫决定把现有的兵分成两部分,从两路进攻。由于有些兵在第一次战斗中受伤了,为了使两部分的兵实力平均些,分的规则是这样的:1)两部分兵的个数最多只能差一个;2)每部分兵的血值总和必须要尽可能接近。现在请你编写一个程序,给定野猫现在有的兵的个数以及每个兵的血格值,求出野猫按上述规则分成两部分后每部分兵的血值总和。

输入格式 Input Format

第一行为一个整数n(1<=n<=200),表示野猫现在有的机枪兵的个数。以下的n行每行一个整数,表示每个机枪兵的血格(1<=ai<=40)。

输出格式 Output Format

只有一行,包含两个数,即野猫的每部分兵的血值总和,较小的一个值放在前面,两个数用空格分隔。

样例输入 Sample Input

3

35

20

32

样例输出 Sample Output

35 52

时间限制 Time Limitation

各个测试点1s

分析

背包问题的变形

首先要正确理解题意:分成两队人,人数最多相差1,就是要把总人数均分,一队为n div 2,另一对为n-n div 2

用搜索的话,只需枚举构造一兵的情况即可,总的状态数大约为C(n)(n div 2),前两组数据都可以搜过,数据中还有一种特殊情况,就是所有兵的攻击力相同。如果骗分程序的话,最多可得30分。

正解当然是动态规划。

如果我们将选择的人数也看成一种体积的话,之后类似装箱问题,将最优性问题转化为判定性问题,可以写出下面的方程:

F[I,j,k]表示前i个人中选了j个人,能否达到k的攻击力。为boolean类型

F[i,j,k]:=f[i-1,j,k] or f[i-1,j-1,k-a[i]]

显然之和第i个人选不选有关,所以说是背包问题的变形。如果是三维的方程,由题目中的数据范围大概是200*100*40*200(注意,题目中特别强调最大攻击力是有提示性的,给出了k循环的上界(j*40),大约305MB(高二的小盆友会算内存吗?)

毫无选择,必须优化。根据0/1背包的原理,我们可以压缩一维状态:

F[j,k]:=f[j,k] or f[j-1,k-a[i]]

当然j和k都需要倒序循环!

计算一下时间复杂度,大约是10^7

这道题不是很难,基本符合noip的要求,但是需要一定的思维量和对背包问题的深刻理解。

program liukeke;
var
  a:array[1..200] of longint;
  f:array[0..200,0..8000] of boolean;
  n,i,sum,k,j,n2:longint;

begin
  readln(n);
  for i:=1 to n do
  begin
    readln(a[i]);
    inc(sum,a[i]);
  end;
  n2:=n div 2;
  f[0,0]:=true;
  for i:=1 to n do
    for j:=n2 downto 0 do
	  for k:=j*40 downto a[i] do
	    f[j,k]:=f[j,k] or f[j-1,k-a[i]];
  for i:=(sum div 2) downto 0 do
    if f[n2,i] then
	begin
	  write(i,' ',sum-i);
	  break;
	end;
end.

反思

这道题中的背包是抽象的,要抽象出一维体积,将问题完美的转化为二维背包。

有时,最优性问题很难求解,要把最优性问题转化成判定性问题。

posted @ 2011-06-16 15:12  liukee  阅读(770)  评论(0编辑  收藏  举报