【NOIP2015模拟11.3】备用钥匙

Description

在一个公司里有n个职员。每一天被分成了m个时刻,每一个职员都会在每天的第Si个时刻出去,第Ti个时候回来。保证同一时刻不会有两个或以上的人出去/回来。
这个公司有一把很神奇的锁,只能从里面开、关,从外面只能用钥匙开、关。现在公司内有k把钥匙,要保证每个人都能回得来的情况下,求门锁着的最长时间。
n<=2000,m<=10^9,k

Solution

我们把每个人的出/进时刻映射下来,对于相邻的两个点,这一段区间有4中情况。
1:左进右进
那么我们只需要让左边右边那个人拿着钥匙就可以得到这段区间的价值。
2:左进右出
没有限制,直接获得。
3:左出右进
两个人都需要拿着钥匙。
4:左出右出
右边那个人拿着钥匙就行了。

那我们把每个人抽象成点,每个点的点权就是他拿着钥匙所能获得的价值。
还有,对于3这种情况,我们从左边那个人向右边那个人连一条边,权值为那个区间的价值。
这样我们发现这样形成了一堆联通块,每个联通块都是一条链!
我们把所有链压平,那么问题就变成了:
有n个点,选择每个点有价值,选择每个点和它的上一个点也有价值,求选出k个点的最大获利。
O(N^2)dp显然

Code

#include
#include
#include
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define N 2005
#define ll long long
using namespace std;
struct note{int x,bz;ll v;}a[N*2];
bool cmp(note x,note y) {return x.v;}
int n,m,k,t[N],x,y,tot,d[N];
ll ans,sum,f[N][N],g[N][N],c[N],v[N];
bool bz[N];
void dfs(int x) {
    if (!x) return;
    bz[x]=1;dfs(t[x]);d[d[0]--]=x;
}
int main() {
    freopen("key.in","r",stdin);
    freopen("key.out","w",stdout);
    scanf("%d%d%d",&n,&m,&k);
    fo(i,1,n) {
        scanf("%d%d",&x,&y);
        a[++tot].x=i;a[tot].bz=1;a[tot].v=x;
        a[++tot].x=i;a[tot].bz=0;a[tot].v=y;
    }
    sort(a+1,a+2*n+1,cmp);sum=a[1].v+m-a[2*n].v;
    fo(i,1,2*n-1) 
        if (a[i].bz==a[i+1].bz) {
            if (a[i].bz) v[a[i].x]+=a[i+1].v-a[i].v;
            else v[a[i+1].x]+=a[i+1].v-a[i].v;  
        } else if (a[i].bz) {
            if (a[i+1].x!=a[i].x) {
                t[a[i].x]=a[i+1].x;bz[a[i+1].x]=1;
                c[a[i+1].x]=a[i+1].v-a[i].v;
            } else v[a[i].x]+=a[i+1].v-a[i].v;
        }
        else sum+=a[i+1].v-a[i].v;
    d[0]=n;
    fo(i,1,n) if (!bz[i]) dfs(i);
    fo(i,1,n)
        fo(j,1,k) {
            if (j>1) f[i][j]=max(g[i-2][j-1],f[i-1][j-1]+c[d[i]]);
            f[i][j]+=v[d[i]];g[i][j]=max(g[i-1][j],f[i][j]);
            ans=max(ans,f[i][j]);
        }
    printf("%lld",ans+sum);
}

你可能感兴趣的:(其他dp)