Description
- 有 n 条线段。
- 每条线段给定其中一端的位置及长度。
- 求所有线段覆盖的最大长度。
- n≤100。
Solution
考虑 dp。
先按照一端的位置进行排序,设 fi,j,0/1 表示前 i 个线段,右端点最靠右的线段是 j 向左/右覆盖,所有线段的最大长度。
直接枚举 i+1 转移可能会出现 i+1 完全覆盖了 j 而无法算贡献,但是思考一下会发现如果 i+1 完全覆盖 j 则把 j 删掉也毫无影响,所以只用钦定 j 没有被完全覆盖,然后枚举下一个对答案有影响的线段。
设 d0 为 k 倒的方向,转移即为:fk,k,d0←fi,j,d+min{posk+d0lenk−(posj+d⋅lenj),lenk}。
但是可能会出现这种情况:

红的是枚举的下一个线段 k,但是可能 k 向左并且存在一个 x 使得 i<x<k 且 posx+lenx>posk,此时 x 也会有贡献,贡献显然为 posx+lenx−posk。
容易发现 x 对答案的贡献只与其右端点有关,并且这些线段一定是向右倒,所以枚举下一个没有完全覆盖的线段 k 时还需要维护位于 (i,k) 这个区间里往右倒的线段的右端点最大值。
转移即为 fk,x,1←fi,j,d+min{posk+d0lenk−(posj+d⋅lenj),lenk}+(posx+lenx−(posk+d0lenk))。
具体见代码。
时间复杂度:O(n2)。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| #include <bits/stdc++.h>
#define int int64_t
const int kMaxN = 105;
int n; int f[kMaxN][kMaxN][2]; std::pair<int, int> a[kMaxN];
void dickdreamer() { std::cin >> n; for (int i = 1; i <= n; ++i) std::cin >> a[i].first >> a[i].second; std::sort(a + 1, a + 1 + n); a[0].first = -1e9; for (int i = 0; i <= n; ++i) { f[i][i][0] = std::max(f[i][i][0], a[i].second); f[i][i][1] = std::max(f[i][i][1], a[i].second); if (i == n) continue; for (int j = 0; j <= i; ++j) { for (int d = 0; d < 2; ++d) { int r = a[j].first + d * a[j].second; f[i + 1][j][d] = std::max(f[i + 1][j][d], f[i][j][d]); std::tuple<int, int, int> t = {-1e9, 0, 0}; for (int tj = i + 1; tj <= n; ++tj) { for (int td = 0; td < 2; ++td) { int tr = a[tj].first + td * a[tj].second; t = std::max(t, {tr, tj, td}); auto [_r, _j, _d] = t; f[tj][_j][_d] = std::max(f[tj][_j][_d], f[i][j][d] + std::min(tr - r, a[tj].second) + _r - tr); } } } } } int ans = 0; for (int i = 1; i <= n; ++i) for (int j = 1; j <= i; ++j) ans = std::max({ans, f[i][j][0], f[i][j][1]}); std::cout << ans << '\n'; }
int32_t main() { #ifdef ORZXKR freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); #endif std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0); int T = 1; while (T--) dickdreamer(); return 0; }
|