JZOJ.4301[NOIP2015模拟11.3]备用钥匙 解题报告

备用钥匙

题目描述

你知道Just Odd Inventions社吗?这个公司的业务是“只不过是奇妙的发明(Just Odd Inventions)”。这里简称为JOI社。

JOI社有N名员工,编号从1到N。所有员工的工作时间从时刻0持续到时刻M,时刻0和时刻M的时候,所有员工都必须在公司内。

某天,出于巧合,JOI社的每个员工都要出行恰好一次。员工i(1<=i<=N)在时刻Si离开公司,时刻Ti回到公司。同一时刻不会同时有两名以上的员工离开或回到公司。
JOI社的入口处有一扇巨大的门,员工只能通过这扇门离开或回到公司。门上挂着一把锁,从公司内部可以任意开锁或上锁,但从公司外部只有持有备用钥匙的人才能开锁或者上锁。时刻0时,锁是锁上的。

每个社员在回到公司的时候,都必须能够进入公司。换句话说,对于任意1<=i<=N,要么员工i持有备用钥匙,要么时刻Ti时门是开着的,否则是不被允许的。员工回到公司的时候,或者携带备用钥匙的员工离开公司的时候,可以选择锁门或不锁。没有携带备用钥匙的员工离开公司的时候没有办法锁门。

JOI社的社长决定把备用钥匙交给N个员工中的K个人。为了避免钥匙的丢失,员工之间不允许借用钥匙。此外,JOI社的社长很重视时间效率,因此每个员工在离开或回到公司的时刻以外,不允许开锁或者上锁。

出于安全的考虑,社长希望上锁的时间越长越好。现在他将员工出入公司的信息和准备交给员工的钥匙数量告诉了你,请你求出在能使所有员工回到公司的时候都能进入公司的大门的前提下,上锁的时间最长是多少。

输入格式

第一行三个空格分隔的整数N,M,K,表示JOI社的员工有N个,工作时间从时刻0到时刻M,备用钥匙有K把。
接下来N行,第i行(1<=i<=N)有两个空格分隔的整数Si,Ti,表示员工i在时刻Si离开公司,时刻Ti回到公司。

输出格式

输出一行一个正整数,表示上锁时间总和的最大值。

样例输入

4 20 2
3 11
5 15
6 10
12 18

样例输出

13

样例解释

JOI社共有4名员工,工作时间为时刻0~时刻M,共有两把备用钥匙。
将钥匙交予员工2和员工4,一天日程如下:
时刻0,锁是关闭状态
时刻3,员工1离开公司。由于员工1没有备用钥匙,无法锁门。
时刻5,员工2离开公司,锁门。
时刻6,员工3离开公司。由于员工3没有备用钥匙,无法锁门。
时刻10,员工3回到公司,不锁门。
时刻11,员工1回到公司,锁门。
时刻12,员工4离开公司,锁门。
时刻15,员工2回到公司,锁门。
时刻18,员工4回到公司,锁门。
直到时刻20为止,锁都保持关闭状态。上锁的时间段为0~3,5~6,11~20,总计13时段,故答案为13。

数据范围

对于20%的数据,1<=N<=20,1<=M<=10^6
对于100%的数据:
1 ≤ N ≤ 2000
1 ≤ M ≤ 10^9
1 ≤ K < N
0 < Si < Ti < M (1≤i≤N)
对于任意 i , j (1≤i≤N , 1≤j≤N , i≠j),Si≠Sj , Si≠Tj , Ti≠Tj

题解

如果一个时间点有员工回来或出去,我们称其为关键时刻。
我们把所有关键时刻按照时间的早晚从前到后排一次序。
很明显,在第一个关键时间点之前和最后一个关键时间点之后的的时间都可以锁门,
这两段时间直接计入答案贡献。

20分算法

接下来我们讨论一下两两时间关键点之间的时间段锁门的条件。

<1>前面的人出去,后面的人进入。(左出右进)
要想中间的这一段时间锁上门,前面的人出去后,得有钥匙锁门。后面的人回来后,需要钥匙开门。
所以锁门的条件为前后两人都需要钥匙。

<2>前面的人出去,后面的人也出去。(左出右出)
要想这一段时间锁门,前面的人出去后,需有钥匙锁门。后面的出去并不需要钥匙。
所以锁门的条件为前面的人需要钥匙。

<3>前面的人进入,后面的人出去。(左进右出)
想要锁门,前面的人进入后把门锁上,后面的人出去并不需要钥匙开门。
所以锁门的条件不需要钥匙。也就是说,这段时间可以直接计入答案贡献。

<4>前面的人进入,后面的人也进入。(左进右进)
要想锁门,前面的人进入后可以把门锁上,后面的人进入需要钥匙开门。
所以锁门的条件是后面的人需要钥匙。

现在我们可以暴力分配钥匙,看看两两时间关键点之间的那段时间满不满足锁门条件,并统计贡献求最大值即可。

100分算法

首先,
我们把第一种情况中的锁门时间贡献计入后面那个人的非个人贡献 Pi 中,并让前面那个人往后面那个人连一条边。(左出右进)
将第二种情况中的锁门时间贡献计入前面那个人的个人贡献 Ci 中。(左进右出)
把第四种情况中的锁门时间贡献计入后面那个人的个人贡献 Ci 中。(左进右进)**

连边以后,会形成许多条链(只能是链,链中可能只有一个点)。我们可以把所有的链压平了,排列在一起。

例如有以下链:

这里写图片描述

设此新形成的序列为 K

接下来我们可以用一个n2的动态规划来实现。
F[i][j]表示钥匙已经分配到了 K 序列中的第i个人,分配了 j 把钥匙,且K序列中的第 i 个人一定分配了一把钥匙,在上述情况下,所获贡献的最大值。

那转移很明显了。
动态转移方程:

F[i][j]:=max(f[1~~i-2][j-1],f[i-1][j-1]+P[[K[i]])+C[K[i]];

因为是由F[1~~i-2][j-1]转移过来说明 Ki1 并没有分配到钥匙,所以不能获得 Ki 的非个人贡献,
而由 F [i-1][j-1]转移过来说明Ki1分配到了钥匙,所以可以获得 Ki 的非个人贡献。
再加上 Ki 分配到了钥匙,所以无论如何都可以获得 Ki 的个人贡献 C [Ki]。
于是转移方程就出来了。

我们看到 F <script type="math/tex" id="MathJax-Element-21">F</script>[1~~i-2][j-1]这一段可以开一个二维数组维护最大值即可。

Code(Pascal)

var
    bz:array[1..2500] of boolean;
    p,c,poi,d:array[0..2000] of int64;
    f,g:array[-2..2010,-1..2000] of int64;
    tim:array[0..4000,1..3] of int64;
    n,j,k,l,i,o:longint;
    hhh,cqy,ans,m:int64;
procedure qsort(l,r:longint);
    var
        i,j:longint;
        m:int64;
    begin
        i:=l;
        j:=r;
        m:=tim[(l+r) div 2,1];
        repeat
            while tim[i,1]<m do inc(i);
            while tim[j,1]>m do dec(j);
            if i<=j then
            begin
                tim[0]:=tim[i];
                tim[i]:=tim[j];
                tim[j]:=tim[0];
                inc(i);
                dec(j);
            end;
        until i>j;
        if l<j then qsort(l,j);
        if i<r then qsort(i,r);
    end;
function max(a,b:int64):int64;
    begin
        if a>b then exit(a)
        else exit(b);
    end;
begin
    assign(input,'key.in'); reset(input);
    assign(output,'key.out'); rewrite(output);
    readln(n,m,k);
    for i:=1 to n do
    begin
        read(tim[i*2-1,1]);
        tim[i*2-1,2]:=1;
        tim[i*2-1,3]:=i;
        readln(tim[i*2,1]);
        tim[i*2,2]:=2;
        tim[i*2,3]:=i;
    end;
    qsort(1,2*n);
    ans:=tim[1,1]+(m-tim[2*n,1]);
    for i:=2 to 2*n do
    if (tim[i-1,2]=2) and (tim[i,2]=1) then ans:=ans+tim[i,1]-tim[i-1,1]
    else if (tim[i-1,2]=1) and (tim[i,2]=2) then
    begin
        if tim[i-1,3]=tim[i,3] then p[tim[i,3]]:=p[tim[i,3]]+tim[i,1]-tim[i-1,1]
        else
        begin
            poi[tim[i-1,3]]:=tim[i,3];
            c[tim[i,3]]:=tim[i,1]-tim[i-1,1];
        end;
    end
    else if (tim[i-1,2]=2) and (tim[i,2]=2) then p[tim[i,3]]:=p[tim[i,3]]+tim[i,1]-tim[i-1,1]
    else p[tim[i-1,3]]:=p[tim[i-1,3]]+tim[i,1]-tim[i-1,1];
    o:=0;
    for i:=1 to n do
    if c[i]=0 then
    begin
        inc(o);
        d[o]:=i;
        cqy:=i;
        while poi[cqy]<>0 do
        begin
            cqy:=poi[cqy];
            inc(o);
            d[o]:=cqy;
        end;
    end;
    for i:=-1 to k do
    begin
        f[0,i]:=-maxlongint;
        g[0,i]:=-maxlongint;
        g[-1,i]:=-maxlongint;
    end;
    g[0,0]:=0;
    f[0,0]:=0;
    for i:=1 to n do
    begin
    f[i,0]:=-maxlongint;
    for j:=1 to k do
    begin
        f[i,j]:=max(g[i-2,j-1],f[i-1,j-1]+c[d[i]])+p[d[i]];
        hhh:=max(f[i,j],hhh);
        g[i,j]:=max(g[i,j],f[i,j]);
        g[i+1,j]:=max(g[i+1,j],g[i,j]);
    end;
    end;
    writeln(ans+hhh);
    close(input);
    close(output);
end.
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值