P5896 Aliens Sol
终于写了这题!
首先,把题意翻译一下,发现每一个点 相当于要覆盖主对角线上 之间的部分。
那么这个题就转化成了一个区间覆盖问题。
看到区间覆盖的最大次数,想到 wqs 二分。
到这里,可以发现如下性质:
- 我们可以按照需要覆盖的区间的左端点进行排序。
- 如果一段需覆盖的区间被另一段需覆盖的区间包含,那么被包含的区间可以被删掉。
下面用 表示预处理完后每一个 代表的需要覆盖的区间。
- 结合第一点与第二点,我们发现排序后,需要选择的区间显然满足 。
- wqs 二分代表的实际上是一个斜率始终 的下凸壳。
对于为什么这个东西是一个下凸壳,我们可以感性理解一下。
设 表示强制选择 个区间时的答案。
如果你只能选择 个区间,那么实际答案最大可以到覆盖全图。
但是如果选择 个区间,你可以在 的基础上,把一个右上角的正方形和左下角的正方形(可以为空)去掉,只选择剩下的图形。
且因为 表示强制选择 个区间时的答案,所以此时一定贪心地选择能去掉大小最多的方案。
当然答案也可能不变,此时对于 的情况答案一定也不变。
考虑到 代表的就是斜率,即斜率单调非降。
那么图象就是个下凸壳了,可以使用 wqs 二分进行求解。
假设当前二分的斜率为 。
设 表示覆盖到第 条线段的最小代价。
若 ,可以使用一个 表示 ;否则 。
观察到含 的项和含 的项相乘,考虑斜率优化。
设 ,且由 转移过来比 更优。
那么有。
发现这个 很烦人,那么在预处理的时候就把 加上 。
把式子拆开。
抵消后,把含 的式子丢到左边,其余丢到右边。
由于 单调递增,故 ,丢到右边要变号。
因为 ,换成正常的斜率形式。
由于 单调递增,故斜率单调递增,用单调队列维护下凸壳即可。
由于 wqs 二分要记录当前选的区间个数,所以还要在答案最小的情况下,最小化选择的区间个数。此时可以加强判定条件,把弹队头的 换成 ,保证当前答案从更早的地方转移过来,一定更优。
时间复杂度 , 表示斜率的值域,即最大答案 。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 10;
int n, m, k, cnt, res, f[N], g[N], q[N], dp[N];
struct Node {
int l, r;
inline bool operator <(const Node &X) const {
if (l == X.l) return r > X.r;
return l < X.l;
}
} p[N];
inline int pf(int x) { return x * x; }
inline int getx(int j1, int j2) { return p[j2 + 1].l - p[j1 + 1].l; }
inline int gety(int j1, int j2) {
int y = f[j1] + pf(p[j1 + 1].l) - g[j1];
int _y = f[j2] + pf(p[j2 + 1].l) - g[j2];
return _y - y;
}
inline bool check(int mid) {
int l, r; q[l = r = 1] = 0;
for (int i = 1; i <= n; ++i) {
while (l < r && 2 * getx(q[l], q[l + 1]) * p[i].r > gety(q[l], q[l + 1])) ++l;
f[i] = f[q[l]] + pf(p[i].r - p[q[l] + 1].l) - g[q[l]] - mid, dp[i] = dp[q[l]] + 1;
while (l < r && gety(q[r - 1], q[r]) * getx(q[r], i) > gety(q[r], i) * getx(q[r - 1], q[r])) --r;
q[++r] = i;
}
return dp[n] <= k;
}
signed main() {
ios_base::sync_with_stdio(false); cin.tie(0), cout.tie(0);
cin >> n >> m >> k;
for (int i = 1; i <= n; ++i) {
cin >> p[i].l >> p[i].r;
if (p[i].l > p[i].r) swap(p[i].l, p[i].r);
}
sort(p + 1, p + 1 + n);
for (int i = 1, r = -1; i <= n; ++i)
if (p[i].r > r) r = p[i].r, p[++cnt] = p[i]; n = cnt;
for (int i = 1; i < n; ++i) if (p[i].r >= p[i + 1].l)
g[i] = pf(p[i].r - p[i + 1].l + 1);
for (int i = 1; i <= n; ++i) ++p[i].r;
int l = -m * m, r = 0, res = 0;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid)) res = f[n] + k * mid, l = mid + 1;
else r = mid - 1;
}
return cout << res << endl, 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现