【动态规划】猫狗大战(背包)
第二题
描述 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.
反思
这道题中的背包是抽象的,要抽象出一维体积,将问题完美的转化为二维背包。
有时,最优性问题很难求解,要把最优性问题转化成判定性问题。