8.17 拼拼图的小杉 1536

题目

背景 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个集合

至于一维,在二维基础上修改即可

O(n2)n2

代码

先附上一个比较容易理解的二维代码

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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值