1 条题解
-
0
自动搬运
来自洛谷,原作者为

Jeefy
要记得生活中有个生,不要只是活着搬运于
2025-08-24 22:47:02,当前版本为作者最后更新于2023-06-02 14:06:52,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
[ROI 2018] Innophone
看了半天网上仅有的一篇题解……才堪堪写出来。
不过在LOJ上看提交,全是
KTT,看得我瑟瑟发抖~~(不会~~。题意翻译
在平面上有一些点,你需要在这个平面上任意确定一个点(不要求是给定的点),定义其贡献为 横坐标 其右侧的点 纵坐标 其左上方的点:

红色部分是右侧的点,蓝色部分是左上方的点。
具体边界参考题目!
我们需要求最大的贡献值。
解题
其实不难发现,目标点的横坐标一定是图中某一个点的横坐标,否则我们可以在固定纵坐标的情况下将点向右移,使得贡献变大。
所以问题简化为如何求蓝色部分的最大贡献?
我们先考虑横坐标固定的情况。
设 表示左侧部分纵坐标大于 的点的个数。那么我们需要求的是
我原本猜测这可能会是一个单峰函数,但是模拟了一组数据:

发现似乎并不单峰……似乎每一次只能暴力求解?
所以我们继续考虑每次加入一个点对于这个函数的影响。
不难发现,其实每一次是把一部分的后缀 变为 。
而对于后缀的 ,或许可以想到分块打标记~~(逃~~。
而考虑每一个块打了标记之后,我们需要求的其实是 的值。
考虑设 ,可以转化为 。
在这个式子中最大化 ,这是我们思考到斜率优化问题。
具体来说,也就是在每一块中维护一个上凸包,又由于 是递增的……依据斜率维护一个双端队列即可。
然而加入一个点的过程呢?我们直接把它所在的块暴力重构即可。
于是我们可以得到更新后的左侧答案
cans,更新总答案即可。复杂度分析
这里我尝试使用核算法 (
accounting method) 进行分析。其实代码中有我用英文写的分析……主要是没有配置输入法……
考虑每一次加入一个点,会重构一个块,花费了 ,而重构凸包,花费是 ,其中有一半是提前为凸包中的元素支付的。这使得在一个块中标记的花费为 。也就是说总的复杂度为 。
这么看着复杂度确实不够优秀,所以这需要代码常数优秀才可。
我犯病有
deque做双端队列……导致开了O2才能过。不过考虑到用
deque对于边界条件的明示作用更大,所以我还是贴上这个写法。代码
#include <iostream> #include <algorithm> #include <deque> #include <cmath> using namespace std; const int N = 2e5; typedef long long lint; // BLO stands for block length, n is the points' count int BLO, n; struct P { int x, y; bool operator < (const P &p) { return x < p.x; } } p[N]; int apps[N << 2], apc = 0; void discrete() { static auto gt = greater<int>(); for (int i = 1; i <= n; ++i) { apps[i] = p[i].y; } sort(apps + 1, apps + 1 + n, gt); int *ae = unique(apps + 1, apps + 1 + n); for (int i = 1; i <= n; ++i) { p[i].y = lower_bound(apps + 1, ae, p[i].y, gt) - apps; } apc = ae - apps - 1; BLO = sqrt(apc); } void read() { cin >> n; for (int x, y, i = 1; i <= n; ++i) { cin >> x >> y; p[i] = {x, y}; } sort(p + 1, p + 1 + n); } // save the sum of one point int s[N]; class BLOCK { private: int l, r, tag; deque<int> Q; // for using slope optimize public: void set(int l, int r) { this->l = l, this->r = r; } // rebuild this block. // with many points that can build a convex tull // which can help use maintain the max value. inline lint build() { lint ans = 0; // we calc every point of the BLOCK, to get the answer for whole block after rebuilding. for (int i = l; i <= r; ++i) { ans = max(ans, 1ll * (s[i] += tag) * apps[i]); } tag = 0; Q.clear(); int siz = 0; for (int i = r; i >= l; --i) { #define Y(i) (1ll * apps[i] * (s[i] + tag)) #define X(i) (1ll * apps[i]) // build reversely, for using the convex hull to right. // now add point (X(i), Y(i)), We should make sure slope(i) >= slope(Q[last]) // Consider Q[siz -1] as j, Q[siz - 2] as k // So we need // ( Y(i) - Y(j) ) / ( X(i) - X(j) ) >= ( Y(j) - Y(k) ) / ( X(j) - X(k) ) // as the followed line while (siz > 1 && ( Y(i) - Y(Q[siz -1]) ) * ( X(Q[siz - 1]) - X(Q[siz - 2]) ) >= ( Y(Q[siz - 1]) - Y(Q[siz - 2]) ) * ( X(i) - X(Q[siz - 1]) )) Q.pop_back(), --siz; Q.push_back(i), ++siz; } return ans; } lint add() { ++tag; while (Q.size() > 1 && Y(Q[0]) <= Y(Q[1])) Q.pop_front(); return Y(Q[0]); } } blocks[700]; lint cans = 0; // now we consider why the complexity is correct? // we using accounting method to analyze it. // First, every time we rebuild a block, we cost sqrt(n) to build // and we build a deque, which at most have sqrt(n) items // This cost 2*sqrt(n) (half prepayed for adding tag). // so when we are adding tag, we cost nothing. // Second, when querying, we use O(1) however. // In total, we have n times add, which makes complexity to O(n \sqrt n) inline void add(int y) { int b = y / BLO; int r = min((b + 1) * BLO, apc + 1); // without r // update one block's s and need rebuild!! for (int i = y; i < r; ++i) { ++s[i]; } cans = max(cans, blocks[b].build()); for (int i = b + 1, ie = apc / BLO; i <= ie; ++i) { cans = max(cans, blocks[i].add()); } } int main() { cin.tie(0)->sync_with_stdio(false); read(); discrete(); for (int i = 0, l = 0, r = BLO - 1; l <= apc; ++i, l += BLO, r += BLO) { blocks[i].set(max(l, 1), min(r, apc)); blocks[i].build(); } lint res = 0; for (int i = 1; i <= n + 1; ++i) { res = max(res, 1ll * (n - i + 1) * p[i].x + cans); // update ans for (n + 1) times !!! if (i <= n) add(p[i].y); } cout << res << '\n'; return 0; }后记
正如我在开头所说,可以使用
KTT解决这道题。而且复杂度非常优秀,是 ,并且跑不满。思路却也与此相似。可以发现,其实每一次操作我们是在 的基础上加后缀加上一个 函数。或者这正也是为什么
KTT可以做这道题的原因。单纯口胡……
- 1
信息
- ID
- 8569
- 时间
- 3000ms
- 内存
- 512MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者