题解:P14410 [JOISC 2015] 钥匙 / Keys

题意

比较繁琐,详见原题

注意一个时间段 \([l,r]\) 的长度为 \(r-l\)

题解

不难的题,模拟赛场切了。

所有 \(s_i,t_i\) 会将 \([0,m]\) 切割成若干个时间段,不妨分类讨论一下每个时间段的左右端点类型,考察其何时对答案有贡献:

  • 若左端点是 \(t_i\),右端点是 \(t_j\),则该时间段对答案有贡献当且仅当 \(j\) 持有钥匙。
  • 若左端点是 \(t_i\),右端点是 \(s_j\),则该时间段一定对答案有贡献,我们直接累加到答案上。
  • 若左端点是 \(s_i\),右端点是 \(s_j\),则该时间段对答案有贡献当且仅当 \(i\) 持有钥匙。
  • 若左端点是 \(s_i\),右端点是 \(t_j\)
    • 如果 \(i=j\),则该时间段对答案有贡献当且仅当 \(i\) 持有钥匙。
    • 如果 \(i\neq j\),则该时间段对答案有贡献当且仅当 \(i,j\) 都持有钥匙。

可以想到,如果没有「\(i,j\) 都持有钥匙」这种贡献条件,那么每个人的贡献都是独立的,我们完全可以对每个人 \(i\) 算出它持有钥匙带来的贡献 \(a_i\),对 \(a\) 求前 \(k\) 大之和。

现在尝试处理「\(i,j\) 都持有钥匙」这种贡献条件,考虑图论建模,对于每条这样的限制,连一条 \(i\to j\) 的有向边,边权为时间段长度。我们发现这样连出来的图一定由若干条链组成(我们把孤点也视作链)。这是因为:

  • 每个点的入度和出度至多为 \(1\)
  • 图中不会连出环。证明可以考察一条链 \(a_1\to a_2\to\cdots\to a_k\),它们在时间轴上的相对顺序必然是 \(s_{a_{k-1}}t_{a_k}\cdots s_2t_3s_1t_2\),显然 \(a_k\) 无法再连向 \(a_1\),因此得证。

显然链之间独立,可以对每条链分开做。对于一条链,问题变成:选择一个点 \(i\)\(a_i\) 的收益,一条边权为 \(w\) 的边两端的点同时被选择有 \(w\) 的收益,要最大化收益。这是简单 DP,令 \(f_{i,j,0/1}\) 表示考虑到链上前 \(i\) 个点,选择了 \(j\) 个点,当前点是否选择的最大价值。转移是容易的:

\[\begin{align*} f_{i,j,0}&=\max(f_{i-1,j,0},f_{i-1,j,1})\\ f_{i,j,1}&=\max(f_{i-1,j-1,0},f_{i-1,j-1,1})+a_i+w(i-1,i) \end{align*} \]

进一步地,可以给所有链钦定一种顺序拼接在一起得到一条长链,拼接处的边权置为 \(0\),在长链上跑上述 DP 即可。时间复杂度为 \(\mathcal{O}(nk)\)

代码
#include <bits/stdc++.h>

using namespace std;

using ll = long long;
using i128 = __int128;
using ui = unsigned int;
using ull = unsigned long long;
using u128 = unsigned __int128;
using ld = long double;
using pii = pair<int, int>;
const int N = 2e3 + 5, mod = 1e9 + 7;
const int inf = 1e9;

template<typename T> inline T lowbit(T x) { return x & -x; }
template<typename T> inline void chk_min(T &x, T y) { x = min(x, y); }
template<typename T> inline void chk_max(T &x, T y) { x = max(x, y); }

int n, m, k, ans, pos[N], a[N << 1], f[2][N][2];
int to[N << 1], deg[N << 1], edge[N << 1];
struct Node { int t, id, tp; } p[N << 1];

int main() {
	ios::sync_with_stdio(0), cin.tie(0);
	cin >> n >> m >> k;
	for (int i = 1, s, t; i <= n; ++i) {
		cin >> s >> t;
		p[i * 2 - 1] = {s, i, 1}, p[i * 2] = {t, i, 0};
	}
	sort(p + 1, p + (n << 1) + 1, [](const Node &x, const Node &y) { return x.t < y.t; });
	for (int i = 1; i <= n << 1; ++i) pos[p[i].id] = i;
	for (int i = 1; i < n << 1; ++i) {
		int d = p[i + 1].t - p[i].t;
		if (!p[i].tp && !p[i + 1].tp) a[pos[p[i + 1].id]] += d;
		else if (p[i].tp && p[i + 1].tp) a[pos[p[i].id]] += d;
		else if (!p[i].tp && p[i + 1].tp) ans += d;
		else {
			if (p[i].id == p[i + 1].id) a[i + 1] += d;
			else ++deg[to[pos[p[i].id]] = pos[p[i + 1].id]], edge[pos[p[i + 1].id]] += d;
		}
	}
	f[0][0][0] = 0;
	for (int i = 1; i <= k; ++i) f[0][i][0] = f[0][i][1] = -inf;
	int cur = 0, prv = 1;
	for (int i = 1; i <= n << 1; ++i) if (!p[i].tp && !deg[i]) {
		for (int p = i; p; p = to[p]) {
			swap(cur, prv);
			for (int j = 0; j <= k; ++j) {
				f[cur][j][0] = max(f[prv][j][0], f[prv][j][1]);
				if (j) f[cur][j][1] = max(f[prv][j - 1][0] + a[p], f[prv][j - 1][1] + a[p] + edge[p]);
				else f[cur][j][1] = -inf;
			}
		}
	}
	cout << max(f[cur][k][0], f[cur][k][1]) << '\n';
	ans += max(f[cur][k][0], f[cur][k][1]);
	cout << ans + p[1].t + m - p[n << 1].t;
	return 0;
}
posted @ 2026-01-23 16:35  P2441M  阅读(0)  评论(0)    收藏  举报