备用钥匙——分情况讨论的好dp
备用钥匙
你知道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
【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。
对于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
分析:
填坑啦!
思考此题暴力时,很显然地要分情况讨论。我们可以画出时间轴来,先将数据按时间排序。接下来考虑相邻两个时间点之间的关系即可。同样很显然的是有4种情况,让我们从易到难:
1.(进,出):取这个时间段的时候,我们可以发现前后两人都可以没有钥匙,也就是(无,无),所以这些时间段是可以直接加进答案里的。
2.(进,进):对于前者可以没有钥匙,可是前者把门锁了后,后者就需要有钥匙了,也就是(无,有),可以记为后者的贡献。
3.(出,出):和2类似的,前者出去锁门需要钥匙,而后者出去可以没有钥匙,也就是(有,无),可以记为前者的贡献。
4.(出,进):根据前面的经验,我们很容易发现这类情况是(有,有),可是并不能和前面一样,因为这里点与点不是独立的了,要取后一个点的贡献的前提是前者也取。
那怎么办呢?因为一个人只有进出两种操作,所以他出现第4种情况的时候至多为2种,我们可以考虑将其连成链,并重新安排顺序使得链连在一块的同时其他数据也在其中。先假设我们完成了这个操作,很显然的有转移方程:f[i][j]=max( max(f[1~i-1][j-1]) , f[i-1][j-1]+w[l[i]])+c[l[i]];前者是不取第4种情况,后者是取第4种情况,而最后加的是2,3情况中单点的贡献。
现在要做的就是打破假设,此处借鉴了一下大佬的代码,也就是dfs部分,我觉得大伙自己体会会更好一点。
代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<queue> 6 #include<algorithm> 7 #include<vector> 8 using namespace std; 9 #define debug printf("zjyvegetable\n") 10 #define int long long 11 inline int read(){ 12 int a=0,b=1;char c=getchar(); 13 while(!isdigit(c)){if(c=='-')b=-1;c=getchar();} 14 while(isdigit(c)){a=a*10+c-'0';c=getchar();} 15 return a*b; 16 } 17 const int N=2e3+50,M=5e3+50; 18 struct node{ 19 int t,fl,id; 20 }q[M]; 21 bool cmp(node x,node y){ 22 return x.t<y.t; 23 } 24 int n,m,k,cnt,tot,c[N],f[N][N],ans,g[N],l[N],line[N],w[N]; 25 bool bz[N]; 26 void dfs(int x){ 27 bz[x]=true; 28 if(line[x])dfs(line[x]); 29 l[tot--]=x; 30 } 31 signed main(){ 32 freopen("key.in","r",stdin); 33 freopen("key.out","w",stdout); 34 n=read();m=read(); 35 k=read(); 36 for(int i=1;i<=n;i++){ 37 q[++cnt].t=read();q[cnt].fl=0;q[cnt].id=i; 38 q[++cnt].t=read();q[cnt].fl=1;q[cnt].id=i; 39 } 40 sort(q+1,q+cnt+1,cmp); 41 ans+=q[1].t+m-q[cnt].t; 42 for(int i=2;i<=cnt;i++){ 43 if(q[i-1].fl==1&&q[i].fl==0){ 44 ans+=q[i].t-q[i-1].t; 45 } 46 else if(q[i-1].fl==0&&q[i].fl==0){ 47 c[q[i-1].id]+=q[i].t-q[i-1].t; 48 } 49 else if(q[i-1].fl==1&&q[i].fl==1){ 50 c[q[i].id]+=q[i].t-q[i-1].t; 51 } 52 else{ 53 if(q[i-1].id==q[i].id){ 54 c[q[i].id]+=q[i].t-q[i-1].t; 55 } 56 else{ 57 line[q[i-1].id]=q[i].id; 58 w[q[i].id]=q[i].t-q[i-1].t; 59 bz[q[i].id]=true; 60 } 61 } 62 } 63 tot=n; 64 for(int i=1;i<=n;i++) 65 if(!bz[i])dfs(i); 66 int maxn=0; 67 for(int i=1;i<=n;i++){ 68 for(int j=1;j<=k;j++){ 69 if(j==1)f[i][j]=max(g[j-1],f[i-1][j-1])+c[l[i]]; 70 else f[i][j]=max(g[j-1],f[i-1][j-1]+w[l[i]])+c[l[i]]; 71 g[j]=max(g[j],f[i-1][j]); 72 } 73 maxn=max(maxn,f[i][k]); 74 } 75 printf("%lld\n",ans+maxn); 76 return 0; 77 }