备用钥匙

Description

你知道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社的社长很重视时间效率,因此每个员工在离开或回到公司的时刻以外,不允许开锁或者上锁。
出于安全的考虑,社长希望上锁的时间越长越好。现在他将员工出入公司的信息和准备交给员工的钥匙数量告诉了你,请你求出在能使所有员工回到公司的时候都能进入公司的大门的前提下,上锁的时间最长是多少。

Input

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

Output

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

Sample Input

4 20 2
3 11
5 15
6 10
12 18

Sample Output

13

HINT

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。

Data Constraint

对于20%的数据,1<=N<=20,1<=M<=10^6
对于100%的数据:
1<=N<=2000
1<=M<=10^9
1<=K

分析

先把每个人的进出情况当成一条线段[Si..Ti]在数轴上表示出来(不配图了)

题目有一个条件:对于任意i,j (1<=i<=N,1<=j<=N,i≠j),Si≠Sj,Si≠Tj,Ti≠Tj
也就是说,上述的线段的端点两两不重合。
N很小,那么可以把这些端点离散化,然后考虑每两个相邻的端点之间的一个区间。对于每一个区间有4种情况:
1. 两端都是外出,那么只要左边的人带钥匙,答案就可以取掉这个区间
2. 两端都是进入,那么只要右边的人带钥匙,答案就可以取掉这个区间
3. 左边进入,右边出去,答案可以直接取掉这个区间
4. 左边出去,右边进来,需要两个人都带钥匙才能取得这个区间(注意可能是同一个人)

这样,枚举哪几个人带钥匙,暴力的20分就可以拿到了!

转换模型

第一个人外出前的时间、最后一个人回来后剩下的时间、以及第3种情况的答案先统计下来。

在第4种情况中,可以发现两个人之间有些关系,我们不妨把每个人看成一个点。对于第4种情况,将两个人连一条价值为该区间长度的边。
因为每个人只会外出/回来1次,显然连完边后得出的是很多条链。

设v[i]为选取第i个点获得的价值,如果选择了两个有边相连的人会有相应的额外价值。题目就转化为:在一张图中有n个点,若干条链,求选取k个点的最大价值。

为了方便代码实现,可以把点一条一条链来重新编号,那么与点i联通的点为i-1 , i+1 ,或者只有1个点i-1或i+1(或者没有点)与它联通

然后可以DP了:
f[i][j] 表示当前选了j个点,最后一个点为i,获得的最大价值,转移:
f[i][j]=max(max(f[k][j1]),f[i1][j]+Ex[i1][i])+v[i] 其中Ex[i-1][i]为i-1与i连边的价值,1≤k≤i-2

注意i-1与i不连通的情况

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn=2005;

int n,m,M,f[maxn][maxn],p[maxn],v[maxn],ex[maxn],e[maxn][2],id[maxn],ch[maxn],sum,r[maxn][3],ans,tot;

bool visit[maxn];

struct data
{
    bool bz;// 0:out 1:in
    int id,x;
}A[maxn*2];

bool cmp(data a,data b)
{
    return a.x<b.x;
}

int main()
{
    freopen("key.in","r",stdin); freopen("key.out","w",stdout);
    scanf("%d%d%d",&n,&M,&m);
    for (int i=0;i<n;i++)
    {
        scanf("%d",&A[tot].x); A[tot].id=i; A[tot++].bz=0;
        scanf("%d",&A[tot].x); A[tot].id=i; A[tot++].bz=1;
    }
    sort(A,A+tot,cmp);
    ans=A[0].x+M-A[tot-1].x;
    memset(e,255,sizeof(e));
    for (int i=1;i<tot;i++)
    {
        if (A[i-1].bz && !A[i].bz) ans+=A[i].x-A[i-1].x;
        else if (A[i-1].bz ^ A[i].bz)
        {
            if (A[i-1].id==A[i].id)
            {
                v[A[i].id]+=A[i].x-A[i-1].x;
                continue;
            }
            if (e[A[i-1].id][0]<0) e[A[i-1].id][0]=A[i].id;else e[A[i-1].id][1]=A[i].id;
            if (e[A[i].id][0]<0) e[A[i].id][0]=A[i-1].id;else e[A[i].id][1]=A[i-1].id;
            r[sum][0]=A[i-1].id; r[sum][1]=A[i].id; r[sum++][2]=A[i].x-A[i-1].x;
        }else if (!A[i].bz) v[A[i-1].id]+=A[i].x-A[i-1].x;else v[A[i].id]+=A[i].x-A[i-1].x;
    }
    tot=0;
    for (int i=0;i<n;i++) if (!visit[i])
    {
        if (e[i][0]<0)
        {
            id[tot++]=i; ch[i]=tot-1; continue;
        }
        if (e[i][1]>=0) continue;
        int last=-1,tr;
        for (int j=i;j>=0;visit[last]=1)
        {
            id[tot++]=j; ch[j]=tot-1;
            tr=(e[j][0]==last);
            last=j; j=e[j][tr];
        }
    }
    for (int i=0;i<sum;i++) ex[r[i][(ch[r[i][0]]<ch[r[i][1]])]]=r[i][2];
    f[0][1]=v[id[0]];
    for (int i=1;i<n;i++)
        for (int j=1;j<=min(i+1,m);j++)
        {
            f[i][j]=p[j-1];
            if (j>1) f[i][j]=max(f[i][j],f[i-1][j-1]+ex[id[i]]);
            f[i][j]+=v[id[i]];
            p[j]=max(p[j],f[i-1][j]);
        }
    ans+=max(p[m],f[n-1][m]);
    printf("%d\n",ans);
    fclose(stdin); fclose(stdout);
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值