题意
抱歉应该不能发
分析
最优解构造方法
不难证明,以下构造方案可以帮助我们构造一组可能的最优解:
选取一个编号
,对于编号在 之间的每个 ,尽量对 部分使用代金券,对其他部分不使用代金券;对于编号在 之间的每个 ,全部使用代金券;对于编号为 的 ,在满足上述约束条件下尽可能多地使用代金券。
考虑
容易发现对于
考虑二分找出最小的符合要求的
仔细观察上面所说的判断
使用线段树找出最优 位置
我们考虑使用万能的线段树解决这个问题。在线段树的每个节点上我们保存对应区间的以下信息:
- 区间和
sum
- 对该区间顺序使用贪心策略后剩余的代金券数量
cnt
- 对该区间顺序使用贪心策略的总花费
cost
- 区间内所有在贪心策略中可以用代金券的部分的和
rest
最后一项可能不是很好理解,这里具体讲一下最后一项 rest
的含义:考虑在贪心策略中我们会对每个 rest
即为在区间内进行贪心策略后每个
线段树节点信息合并方法也非常简单:
inline Node operator+(const Node& oth) const { return (Node){this->sum + oth.sum, this->cnt + oth.cnt - min(cnt, oth.rest), this->cost + oth.cost - min(cnt, oth.rest), this->rest + oth.rest - min(cnt, oth.rest)}; }
合并过程采用了类似 CDQ 分治的思想,每次合并两个相邻区间的信息时计算左区间对右区间产生的贡献,在本题中即为使用左区间剩余的代金券抵消右区间尚未抵消并还能被抵消的部分。
接下来只需要在线段树上找出最小的使得
判断 位置的方案
根据构造方案,
二分在
代码
时间复杂度
/** * @author Macesuted (macesuted@qq.com) * @copyright Copyright (c) 2021 */ #include <bits/stdc++.h> using namespace std; namespace io { // fread } // namespace io using io::getch; using io::getstr; using io::putch; using io::putstr; using io::read; using io::write; #define maxn 300005 struct Node { long long sum, cnt, cost, rest; inline Node operator+(const Node& oth) const { return (Node){this->sum + oth.sum, this->cnt + oth.cnt - min(this->cnt, oth.rest), this->cost + oth.cost - min(this->cnt, oth.rest), this->rest + oth.rest - min(this->cnt, oth.rest)}; } }; int n; long long c; long long a[maxn]; Node tree[maxn << 2]; void build(int p, int l, int r) { if (l == r) return tree[p].sum = tree[p].cost = a[l], tree[p].cnt = a[l] / c, tree[p].rest = a[l] % c, void(); int mid = (l + r) >> 1; build(p << 1, l, mid), build(p << 1 | 1, mid + 1, r); return tree[p] = tree[p << 1] + tree[p << 1 | 1], void(); } void update(int p, int l, int r, int qp, long long val) { if (l == r) return tree[p].sum = tree[p].cost = val, tree[p].cnt = val / c, tree[p].rest = val % c, void(); int mid = (l + r) >> 1; qp <= mid ? update(p << 1, l, mid, qp, val) : update(p << 1 | 1, mid + 1, r, qp, val); return tree[p] = tree[p << 1] + tree[p << 1 | 1], void(); } Node getAns(int p, int l, int r, int ql, int qr) { if (ql <= l && r <= qr) return tree[p]; int mid = (l + r) >> 1; Node answer = (Node){0, 0, 0, 0}; if (ql <= mid) answer = answer + getAns(p << 1, l, mid, ql, qr); if (qr > mid) answer = answer + getAns(p << 1 | 1, mid + 1, r, ql, qr); return answer; } long long work(void) { int p = 1, l = 1, r = n; Node left = (Node){0, 0, 0, 0}, right = (Node){0, 0, 0, 0}; while (l != r) if ((left + tree[p << 1]).cnt >= (tree[p << 1 | 1] + right).sum) { right = tree[p << 1 | 1] + right; p = p << 1, r = (l + r) >> 1; } else { left = left + tree[p << 1]; p = p << 1 | 1, l = ((l + r) >> 1) + 1; } long long cnt = 0, rig = 0; if (l > 1) cnt = getAns(1, 1, n, 1, l - 1).cnt; if (l < n) rig = getAns(1, 1, n, l + 1, n).sum; long long vl = 0, vr = cnt + 1; while (vl + 1 < vr) { long long mid = (vl + vr) >> 1; (cnt + (a[l] - mid) / c - mid >= rig) ? vl = mid : vr = mid; } long long answer = a[l] - vl; if (l > 1) answer += getAns(1, 1, n, 1, l - 1).cost; return answer; } int main() { n = read<int>(); int q = read<int>(); c = read<long long>(); for (register int i = 1; i <= n; i++) a[i] = read<long long>(); build(1, 1, n); write(work()), putch('\n'); while (q--) { int x = read<int>(); update(1, 1, n, x, a[x] = read<long long>()); write(work()), putch('\n'); } return 0; }
Comments | 1 条评论
Orz