题解:P10784 【MX-J1-T4】『FLA - III』Wrestle

· · 题解

我竟然自己一个人把这道题切出来了(花了两天,只算零碎时间是 1 个小时)。算是考前给自己的信心。写完了还是有些感触的,故写篇题解祭。

先读题目。

因为是处理线段关系,所以不管三七二十一,先把蓝色线段按照 L_i 为关键字排序,红色线段按照 l_i 为关键字排序。

每个点至多被一个红色线段和一个蓝色线段覆盖。说明蓝色线段之间不会有交集,红色线段亦然。故 r_i<l_{i+1}R_i<L_{i+1}

定义 s_i 表示第 s_i 个红色线段为最左的和第 i 个蓝色线段有交集的线段,t_i 表示第 t_i 个红色线段为最右的和第 i 个蓝色线段有交集的线段。因为 L_i 的单调性且同色线段互不相交,所以可以通过二分求解 s_it_i

选取线段是有代价的,即为有交的红色线段的权值。也是有收益的,即为交集长度。定义 v_i 为选取第 i 个蓝色线段的收益,c_i 为选取第 i 个蓝色线段的代价。根据题目,c_i=\sum\limits_{k=s_i}^{t_i} w_k

而第 i 个蓝色线段选取的收益就比较复杂——要处理边际。如果有 l_{s_i}<L_ir_{t_i}>R_i,则需要特殊处理。中间的皆为连续的,加上 \sum\limits_{k=s_i+1}^{t_i-1}r_k-l_k+1

这两个求和,用前缀和优化。

再将 s,t,v,c 按照 s_i 排序。

而每个红色线段至多被一个蓝色线段覆盖。所以选取的蓝色线段的 [s_i,t_i] 不可以相交

问题转化成了:有一些线段,代价为 c_i,收益为 v_i,起始点为 s_i,终止点为 t_i。求选取一些线段的最大收益,使得总代价不超过 k,且线段两两不相交(这里没有什么蓝色红色线段了)。

等等,这不就是 01 背包吗!

但因为不可以相交,所以方程为:

f_{i,j}=\max_{t_k<s_i}\{f_{k,j-c_i}+v_i\}

而这样的时间复杂度是 \mathcal{O}(m^2k),只能拿 50 分。考虑优化。

$$ f_{i,j}=\max\{F_{k,j}\} $$ 时间复杂度 $\mathcal{O}(mk)$。 因此易得代码: ```cpp #include <bits/stdc++.h> #define rty printf("Yes\n"); #define RTY printf("YES\n"); #define rtn printf("No\n"); #define RTN printf("NO\n"); #define rep(v,b,e) for(int v=b;v<=e;v++) #define repq(v,b,e) for(int v=b;v<e;v++) #define rrep(v,e,b) for(int v=b;v>=e;v--) #define rrepq(v,e,b) for(int v=b;v>e;v--) #define stg string #define vct vector using namespace std; typedef long long ll; typedef unsigned long long ull; void solve() { } const int N = 2E5 + 5; struct RedSegment { int l, r; ll w; }reds[N]; struct BlueSegment { int l, r; ll v, w; }blues[N]; ll reds_pref[N], reds_len[N], dp[5005][5005], cnt, dp_pre[5005][5005], blue_l[5005]; ll cover[N]; bool cmp(RedSegment a, RedSegment b) { return a.l < b.l; } bool check(int red, pair<int, int> blue) { return max(blue.first, reds[red].l) <= min(blue.second, reds[red].r); } bool cmp2(BlueSegment a, BlueSegment b) { return a.l < b.l; } main() { // int t; cin >> t; while (t--) solve(); int n, m, k; cin >> n >> m >> k; rep(i, 1, n) cin >> reds[i].l >> reds[i].r >> reds[i].w; sort(reds + 1, reds + n + 1, cmp); rep(i, 1, n) { reds_pref[i] = reds_pref[i - 1] + reds[i].w; // 权值的前缀和 reds_len[i] = reds_len[i - 1] + reds[i].r - reds[i].l + 1; // 收益的前缀和 } rep(i, 1, m) { int lef, rig; cin >> lef >> rig; int left_red = 1, r = n; int l = 1; while (l < r) { int mid = (l + r + 1) / 2; if (reds[mid].l > lef) r = mid - 1; else l = mid; } left_red = max(1, l); if (!check(left_red, {lef, rig})) { left_red++; } // 二分 si int right_red = 1; l = left_red, r = n; while (l < r) { int mid = (l + r) / 2; if (reds[mid].r < rig) l = mid + 1; else r = mid; } right_red = min(n, r); if (!check(right_red, {lef, rig})) right_red--; // 二分 ti ll v = 0, w = 0; // 计算收益与代价 if (right_red - left_red >= 0) { w = reds_pref[right_red] - reds_pref[left_red - 1]; // 代价易得 int tmpl = left_red, tmpr = right_red; if (reds[left_red].l < lef) { v = min(rig, reds[left_red].r) - max(reds[left_red].l, lef) + 1; tmpl++; } if (tmpr >= tmpl && reds[right_red].r > rig) { v += min(rig, reds[right_red].r) - max(lef, reds[right_red].l) + 1; tmpr--; } // 收益需要处理边际 if (tmpr - tmpl >= 0) v += reds_len[tmpr] - reds_len[tmpl - 1]; blues[++cnt] = {left_red, right_red, v, w}; } } sort(blues + 1, blues + cnt + 1, cmp2); rep(i, 1, cnt) { blue_l[i] = blues[i].l; // 因为我二分很烂 qwq,单开一个数组可以用 upper_bound( } ll ans = 0; rep(i, 1, cnt) { int ending = upper_bound(blue_l + 1, blue_l + cnt + 1, blues[i].r) - blue_l; rep(j, 0, k) { dp_pre[i][j] = max(dp_pre[i - 1][j], dp_pre[i][j]); if (j < blues[i].w) continue; dp[i][j] = dp_pre[i][j - blues[i].w] + blues[i].v; dp_pre[ending][j] = max(dp_pre[ending][j], dp[i][j]); ans = max(ans, dp[i][j]); // dp_pre 为 F 数组 // 动规优化 } } cout << ans; return 0; } ```