题目
背景 Background
小杉的幻想来到了经典日剧《死亡拼图》的场景里……
被歹徒威胁,他正在寻找拼图
突然广播又响了起来,歹徒竟然又有了新的指示。
小杉身为新一代的汤浅,有责任带领大家脱离危险!
(若对情节有任何疑问,请观看原剧)
描述 Description
歹徒告诉小山,他正在寻找的拼图块其实可以拼成N幅有顺序的拼图。
每幅完整的拼图由若干块拼图块组成,输入中包含每幅拼图所含的拼图块数目。
歹徒要求小杉把所有幅拼图按给出的顺序划分成M个集合,一个拼图集合由若干幅完整的拼图组成,并且总的拼图块的数目不超过T。并且,构成集合的拼图是不能交叉的,例如,当完整拼图1与完整拼图3被放在拼图集合1中之后,完整拼图2就只能放进拼图集合1或者不放进任何拼图集合。
小杉要找出划分成M个集合后,M个集合中最多能有多少幅完整的拼图。
意思就是:
对于n个数,把它们划分成m段,每段的数之和必须小于等于t,可以抛弃任意数字,求最多能留下多少个数。
题解
一看就知道是动态规划啦(知道有什么用,能得分吗)
刚开始考虑的是f[i,j]表示取到第i个数,已经划分了j段,然而题意都理解错了,直接WA
看了题解,恍然大悟:原来如此!(真的是恍然大悟吗)
f[i,j]表示前i个数取了j个最少需要的集合个数
a[i,j]表示前i个数取了j个时当前段中的的数大小
然后考虑状态转移
此时可以效仿01背包,每个数都可以考虑取和不取两种状态,若不取就是f[i,j]=f[i-1,j],a[i,j]=a[i-1,j];若取就麻烦一点
1.上一个集合放不下了,即a[i-1,j-1]+a[i]>t,则新开一个集合,f[i,j]=f[i-1,j-1]+1
2.上一个集合可以放,即a[i-1,j-1]+a[i]<=t,则直接放入上一个集合,f[i,j]=f[i-1,j-1]
注意要判断取和不取那种情况更优
找答案时应在f[n,i](a[n,i]<=m)中找——为什么小于m也可以?因为设m=4,若可以分成3个集合,很显然也可以分成4个集合
至于一维,在二维基础上修改即可
代码
先附上一个比较容易理解的二维代码
var
f,a:array[0..1005,0..1005]of longint;
b:array[0..1005]of longint;
n,m,t,i,j,k:longint;
begin
readln(n,m,t);
for i:=1 to n do
read(b[i]);
fillchar(f,sizeof(f),$7f);
f[0,0]:=1;a[0,0]:=0;
for i:=1 to n do
for j:=1 to i do
begin
if (a[i-1,j-1]+b[i]<=t) then
begin
f[i,j]:=f[i-1,j-1];
a[i,j]:=a[i-1,j-1]+b[i];
end
else
if a[i-1,j-1]+b[i]>t then
begin
f[i,j]:=f[i-1,j-1]+1;
a[i,j]:=b[i];
end;
if (f[i-1,j]<f[i,j])or(f[i-1,j]=f[i,j])and(a[i-1,j]<a[i,j]) then
begin f[i,j]:=f[i-1,j];a[i,j]:=a[i-1,j];end;
end;
for i:=1 to n do
if (f[n,i]<=m) then k:=i;
writeln(k);
end.
这个一维代码是在二维代码的基础上修改的,时间差不多,可以一定程度上省内存
var
f,a:array[0..1005]of longint;
b:array[0..1005]of longint;
n,m,t,i,j,k:longint;
begin
readln(n,m,t);
for i:=1 to n do
read(b[i]);
fillchar(f,sizeof(f),$7f);
f[0]:=1;a[0]:=0;
for i:=1 to n do
for j:=i downto 1 do
begin
if (a[j-1]+b[i]<=t) then
begin
if (f[j-1]<f[j])or(f[j-1]=f[j])and(a[j-1]+b[i]<a[j]) then
begin
f[j]:=f[j-1];
a[j]:=a[j-1]+b[i];
end;
end
else
if a[j-1]+b[i]>t then
if (f[j-1]+1<f[j])or(f[j-1]+1=f[j])and(b[i]<a[j]) then
begin
f[j]:=f[j-1]+1;
a[j]:=b[i];
end;
end;
for i:=1 to n do
if (f[i]<=m) then k:=i;
writeln(k);
end.