NOIP2010初赛 烽火传递

5 篇文章 0 订阅
2 篇文章 0 订阅

Description

  烽火台又称烽燧,是重要的军事防御设施,一般建在险要或交通要道上。一旦有敌情发生,白天燃烧柴草,通过浓烟表达信息;夜晚燃烧干柴,以火光传递军情,在某两座城市之间有  n    n   个烽火台,每个烽火台发出信号都有一定代价。为了使情报准确地传递,在连续  m    m   个烽火台中至少要有一个发出信号。请计算总共最少花费多少代价,才能使敌军来袭之时,情报能在这两座城市之间准确传递。

Input

  第一行:两个整数  NM   N , M 。其中N表示烽火台的个数,  M    M   表示在连续  m    m   个烽火台中至少要有一个发出信号。接下来  N    N   行,每行一个数  Wi   W i ,表示第i个烽火台发出信号所需代价。

Output

  一行,表示答案。

Sample Input

5 3
1
2
5
6
2

Sample Output

4

Data Constraint

对于50%的数据, MN1,000 M ≤ N ≤ 1 , 000 。 对于100%的数据, MN100,000Wi100 M ≤ N ≤ 100 , 000 , W i ≤ 100

Solution

  很明显,对于第  i    i   个烽火台有两种状态——点燃与不点燃。
  因此我们设  fi,0    f i , 0    fi,1   f i , 1  fi,0    f i , 0   表示前  i    i   个烽火台如果第  i    i   个不点燃需要付出的最小代价,  fi,1   f i , 1 则表示点燃第  i    i   个的最小代价。
  如果第i个烽火台不点燃,那么在 i i 之前的m1个烽火台必须至少有一个要点燃的,也就是说, fi,0=min(fik,1),k[1,m1] f i , 0 = m i n ( f i − k , 1 ) , k ∈ [ 1 , m − 1 ]
  如果点燃,当前最小代价就是前 m m 个的最小值加第i个的代价,即 fi,1=min(fik,1+ci),k[1,m] f i , 1 = m i n ( f i − k , 1 + c i ) , k ∈ [ 1 , m ] 注意这里包括第  m    m   个。
  但是有个问题。
  如果每次都枚举,那么复杂度则变为  n2    n 2   ,不超时才怪。
  所以,这里就是单调队列的登场\(^o^)/
  单调队列,简单地说,就是能快速寻找某点前的最大/最小值的玩意儿。
  对于这题来讲,当然是维护使  dt1<dt2<...<dti    d t 1 < d t 2 < . . . < d t i   就可解决问题了。

单调队列进出队操作

  进队时,将进队的元素为  e    e   ,从队尾往前扫描,直到找到一个不大于  e    e   的元素  d    d   ,将  e    e   放在  d    d   之后,舍弃  e    e   之后的所有元素;如果没有找到这样一个  d    d   ,则将  e    e   放在队头(此时队列里只有这一个元素)。
  出队时,将出队的元素为  e    e   ,从队头向后扫描,直到找到一个元素  f    f    e    e   后进队,舍弃  f    f   之前所有的。(实际操作中,由于是按序逐个出队,所以每次只需要出队只需要比较队头)。

附:此篇为笔者人生第一篇博客,内容和排版这些细节先不必在意啦。

CODE

var     n,m,i,j,head,tail,j2:longint;
        a:array[1..100000] of longint;
        k:array[1..100000,1..2] of longint;
        f:array[1..100000,0..1] of longint;
        p:boolean;
function min(x,y:longint):longint;
begin
        if x<y then exit(x) else exit(y);
end;
begin

        readln(n,m);
       // for i:=1 to n do f[i,0]:=maxlongint;
        for i:=1 to n do readln(a[i]);
        f[1,1]:=a[1];
        f[2,0]:=a[1];
        f[2,1]:=a[2];
        k[1,1]:=a[1];
        k[1,2]:=1;
        tail:=1;
        head:=1;
        for i:=3 to m do begin
                f[i,1]:=a[i];
                p:=true;
                for j:=tail downto head do if k[j,1]<=f[i-1,1] then begin
                        p:=false;
                        k[j+1,1]:=f[i-1,1];
                        k[j+1,2]:=i-1;
                        tail:=j+1;
                        break;
                end;
                if p then begin
                        k[head,1]:=f[i-1,1];
                        k[head,2]:=i-1;
                        tail:=head;
                end;
                f[i,0]:=k[head,1];
        end;
        for i:=m+1 to n do begin
                p:=true;
                for j:=tail downto head do if k[j,1]<=f[i-1,1] then begin
                        p:=false;
                        k[j+1,1]:=f[i-1,1];
                        k[j+1,2]:=i-1;
                        tail:=j+1;
                        break;
                end;
                if p then begin
                        k[head,1]:=f[i-1,1];
                        k[head,2]:=i-1;
                        tail:=head;
                end;
                f[i,1]:=k[head,1]+a[i];
                for j:=head to tail do begin
                        if k[head,2]=i-m then begin
                                for j2:=head to tail do if k[j2,2]>k[head,2] then begin
                                        head:=j2;
                                        break;
                                end;
                        end;
                end;
                f[i,0]:=k[head,1];
        end;
        writeln(min(f[n,1],f[n,0]));

end.
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值