卧薪尝胆,厚积薄发。
生生死死
Date: Wed Mar 06 15:19:58 CST 2019 In Category: NoCategory

Description:

有 $n$ 个人,第 $i$ 个人在 $l_i$ 离开 $r_i$ 回来,有 $k$ 把钥匙,每个人回来的时候如果他没有钥匙门必须是开的,有钥匙的人可以在离开之后锁门,回来的人也可以选择锁上门或者让门继续开着,问门开着的时间最少是多少。
$1\leqslant k\leqslant n\leqslant 2000$

Solution:

首先发现一个有钥匙的人能控制的范围是从 $l_i$ 开始到下一个发生时间的时刻,只有这部分的开关是由这个人决定的,由于人回来后也可以随便开关,我们可以把结束点也看成是一次决策是否开关的过程,然后如果某个人没有钥匙,那么控制 $r_i$ 的那个区间必须不能关,我们可以发现这形成了一个依赖关系,依赖关系形成了一个树形结构,我们可以在这棵树上树形 $DP$ 一下,也就是设 $f[i][j][0/1]$ 表示子树 $i$ 内分配了 $j$ 个钥匙,根节点有没有钥匙,我们先假设有钥匙一定关门,那么 $0$ 只能朝 $0$ 转移, $1$ 随意,这也就是一个背包,然后可以发现我们可以给一个人钥匙但是不关门,于是我们让 $0$ 统一往 $1$ 转移一下就行了。

Code:


#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cctype>
#include<cstring>
using namespace std;
int n,k;
#define MAXN 4010
int l[MAXN],r[MAXN];
int s[MAXN];
int val[MAXN];//if you don't choose k,you may get val[k] as cost
int fa[MAXN];//if you don't choose k,you mustn't choose fa[k]
bool tag[MAXN];//whether k is limited
struct edge
{
int to,nxt;
}e[MAXN];
int edgenum = 0;
int lin[MAXN] = {0};
void add(int a,int b)
{
e[++edgenum] = (edge){b,lin[a]};lin[a] = edgenum;
return;
}
int siz[MAXN];
int f[MAXN][MAXN][2];
void dp(int k)
{
memset(f[k],0x3f,sizeof(f[k]));
siz[k] = 1;
if(tag[k])f[k][1][1] = 0,f[k][0][0] = val[k];
else f[k][0][1] = 0,f[k][0][0] = val[k];
for(int i = lin[k];i != 0;i = e[i].nxt)
{
dp(e[i].to);
siz[k] += siz[e[i].to];
for(int j = siz[k];j >= 0;--j)
{
int val1 = 0x3f3f3f3f,val0 = 0x3f3f3f3f;
for(int l = 0;l <= min(siz[e[i].to],j);++l)
{
val1 = min(val1,f[k][j - l][1] + f[e[i].to][l][1]);
val0 = min(val0,f[k][j - l][0] + min(f[e[i].to][l][0],f[e[i].to][l][1]));
}
f[k][j][1] = val1;
f[k][j][0] = val0;
}
}
for(int l = 1;l <= siz[k];++l)f[k][l][1] = min(f[k][l][1],f[k][l - 1][0]);
return;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i = 1;i <= n;++i)scanf("%d%d",&l[i],&r[i]);
for(int i = 1;i <= n;++i)s[++s[0]] = l[i],s[++s[0]] = r[i];
sort(s + 1,s + 1 + s[0]);
for(int i = 1;i <= n;++i)
{
int lef = lower_bound(s + 1,s + 1 + s[0],l[i]) - s;
int rig = lower_bound(s + 1,s + 1 + s[0],r[i]) - s;
val[lef] = s[lef + 1] - s[lef];
fa[lef] = rig - 1;
tag[lef] = true;
bool flag = false;
for(int j = 1;j <= n;++j)if(l[j] < r[i] && r[i] < r[j])flag = true;
if(flag)val[rig] = s[rig + 1] - s[rig];
else val[rig] = 0;
fa[rig] = rig;
tag[rig] = false;
}
for(int i = 1;i <= 2 * n;++i)
{
if(fa[i] != i)add(fa[i],i);
else add(0,i);
}
dp(0);
int ans = 0x3f3f3f3f;
for(int i = 0;i <= k;++i)ans = min(ans,min(f[0][i][0],f[0][i][1]));
cout << ans << endl;
return 0;
}

Solution2:

另一个做法:
考虑每一条线段,如果两边都是起点,就给左边的考察队加上线段长度,如果都是终点,就给右边的考察队加上线段长度,如果左边终点右边起点,一定会有贡献,如果左边起点右边终点,就在两个点之间连一条权值为线段长度的边,权值代表给这个考察队钥匙可以获得的收益,然后问题就变成了这个图的点数不超过 $k$ 的最大权子图,发现这个图由很多条链构成,因此在链上 $DP$ 就行了。

Code2:


没有代码
In tag: DP-树形DP
Copyright © 2020 wjh15101051
ღゝ◡╹)ノ♡