【NOIP2015模拟11.3】备用钥匙

本文探讨了在员工需按时出入公司并确保安全的前提下,如何最优地分配备用钥匙,以最大化大门上锁时间。通过分析员工的出入时间序列,采用动态规划算法求解最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

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

分析

首先把所有进出时间都放在坐标系下,发现相邻的两个点会有4种情况:
第一种:左出右出
只需要左端点那个人带钥匙该区间便可以获得其中的值。
第二种:左进右进
右端点那个人带钥匙该区间便可以获得其中的值。
第三种:左进右出
都不用拿,直接计入答案。
第四种:左出右进
两人都带钥匙该区间便可以获得其中的值。
对于前两种情况,设v[i]表示当i这个人拿了钥匙时,可以给答案贡献多少;
而第四种情况,可以将两个人连一条边,然后就会发现很多个联通块,而且都是以一条链的形式。
然后,所有的链排在一起,给每一个点按位置一个新的编号。
问题转化为,有n个点,选取每个点有得分,并且同时选取一个点以及它的上一个点有额外得分,求选出k 个点的最高得分。
就可以用dp解决:
f[i][j]表示前i个点,选了j个点,其中包括i,的最大得分;
f[i][j]=max(max(f[1~i-2][j-1]),f[i-1][j-1]+当选取了i-1和i时可以获得的额外得分)+v[i]
其中max(f[1~i-2][j-1])可以开一个动态数组表示:
g[j]表示当前做到i的max(f[1~i-2][j])。
然后就变成了
f[i][j]=max(g[j-1],f[i-1][j-1]+当选取了i-1和i时可以获得的额外得分)+v[i]

#include <cmath>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <queue>
const int maxlongint=2147483647;
using namespace std;
int g[4005],v[4005],tog[4005],f[4005][4005],b[4005][5],n,m,tot,ans,k,mx,d[4005],next[4002];
bool bz[4002];
void q(int l,int r)
{
    int i=l,j=r,mid=b[(l+r)/2][2],e;
    while(i<j)
    {
        while(b[i][2]<mid) i++;
        while(b[j][2]>mid) j--;
        if(i<=j)
        {
            e=b[i][2];
            b[i][2]=b[j][2];
            b[j][2]=e;
            e=b[i][0];
            b[i][0]=b[j][0];
            b[j][0]=e;
            e=b[i][1];
            b[i][1]=b[j][1];
            b[j][1]=e;
            i++;
            j--;
        }
    }
    if(i<r) q(i,r);
    if(l<j) q(l,j);
}
int dg(int x)
{
    if(!x)
        return 0;
    bz[x]=false;
    dg(next[x]);
    d[d[0]--]=x;
}
int main()
{
    int i;
    scanf("%d%d%d",&n,&m,&k);
    tot=0;
    for(i=1;i<=n;i++)
    {
        scanf("%d",&b[++tot][2]);
        b[tot][1]=i;
        b[tot][0]=0;
        scanf("%d",&b[++tot][2]);
        b[tot][1]=i;
        b[tot][0]=1;
    }
    q(1,tot);
    ans=0;
    memset(bz,true,sizeof(bz));
    for(i=1;i<=tot-1;i++)
    {
        int x=b[i][0]*10+b[i+1][0];
        if(x==0)
        {
            v[b[i][1]]+=b[i+1][2]-b[i][2];
        }
        else
        if(x==11)
        {
            v[b[i+1][1]]+=b[i+1][2]-b[i][2];
        }
        else
        if(x==10)
        {
            ans+=b[i+1][2]-b[i][2];
        }
        else
        {
            if(b[i][1]!=b[i+1][1])
            {
                next[b[i][1]]=b[i+1][1];
                bz[b[i+1][1]]=false;
                tog[b[i+1][1]]=b[i+1][2]-b[i][2];
            }
            else
            {
                v[b[i][1]]+=b[i+1][2]-b[i][2];
            }
        }
    }
    ans+=b[1][2];
    ans+=m-b[tot][2];
    d[0]=n;
    for(i=1;i<=n;i++)
        if(bz[i])
            dg(i);
    for(i=1;i<=n;i++)
    {
        for(int j=1;j<=k;j++)
        {
            if(j>1)
                f[i][j]=max(g[j-1],f[i-1][j-1]+tog[d[i]]);
            f[i][j]+=v[d[i]];
            g[j]=max(g[j],f[i-1][j]);
            mx=max(mx,f[i][j]);
        }
    }
    printf("%d\n",mx+ans);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值