题解:P14410 [JOISC 2015] 钥匙 / Keys
记录第一篇在洛谷的题解诞生
[GCJ 2015 #1A] Mushroom Monster 题解
题意简述
某公司有
题目分析
容易发现,公司大门是否要关闭将影响着后一个将进出公司大门的员工,所以将所有员工的离开和返回事件按时间排序后,相邻两个事件之间的时间间隔即可称为一个时间段,每个时间段内的门要么一直开着,要么一直关着。
我们可以通过分析相邻时间间隔上的事件情况来确定公司大门关闭的状态(某些情况下有无钥匙都可以关闭的不分配钥匙直接产生时间贡献,其他情况需要考虑给钥匙还是不给钥匙,此处可以考虑动态规划处理)。
算法设计
- 第
i 个人分配钥匙:dp_{i,j,1} = \max(dp_{i-1,j-1,0} + v_{i}, dp_{i-1,j-1,1} + v_{i} + w_{i})
初始时
复杂度分析
排序的时间复杂度为
非常容易理解的代码
#include <bits/stdc++.h>
using namespace std;
const int N = 4005;
int n, m, k, ans, w[N], v[N], nxt[N], dp[2][N][2];
struct node {
int tim, id, flag;//flag: 0-离开, 1-返回
bool operator < (const node &b) const {
return tim < b.tim;
}
} a[N];
int main() {
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) {
cin >> a[2 * i - 1].tim;
a[2 * i - 1].id = i, a[2 * i - 1].flag = 0;// 离开
cin >> a[2 * i].tim;
a[2 * i].id = i, a[2 * i].flag = 1; //返回
}
n *= 2;
sort(a + 1, a + n + 1);
ans = a[1].tim + m - a[n].tim;
for (int i = 1; i < n; i++) {
int tmp = a[i + 1].tim - a[i].tim;
if (a[i].flag && !a[i + 1].flag) //返回--离开:可以关闭
ans += tmp;
else if (a[i].flag && a[i + 1].flag) //返回--返回:i+1有钥匙则可以关闭
v[a[i + 1].id] += tmp;
else if (!a[i].flag && !a[i + 1].flag)//离开--离开:第一个离开的人有钥匙可以关闭
v[a[i].id] += tmp;
else if (!a[i].flag && a[i + 1].flag) { //返回--离开:
if (a[i].id == a[i + 1].id) //同一个人
v[a[i].id] += tmp;
else //不同员工:如果两人都有钥匙,可以关闭
nxt[a[i].id] = a[i + 1].id, w[a[i + 1].id] += tmp;
}
}
//员工时间顺序链
int head = 0;
for (int i = 1; i <= n; i++) {
if (!w[i]) {
nxt[head] = i;
while (nxt[head]) {
head = nxt[head];
}
}
}
dp[0][0][0] = ans;
int s = 0;
for (int i = nxt[0]; i; i = nxt[i]) {
s ^= 1;
for (int j = 0; j <= k; j++) {
dp[s][j][0] = max(dp[s ^ 1][j][0], dp[s ^ 1][j][1]);
if (j > 0)
dp[s][j][1] = max(dp[s ^ 1][j - 1][0] + v[i], dp[s ^ 1][j - 1][1] + v[i] + w[i]);
}
}
cout << max(dp[s][k][0], dp[s][k][1]) << endl;
}
声明:本文在 Markdown 排版上使用了生成式 AI 工具进行辅助。