小 C 的屏幕保护程序 / COCI 2012~2013 CONTEST #4 Task AKVARIJ 题解
前言
题意简述
给你一个长为 \(n\) 的鱼缸的横截面。对其建立平面直角坐标系,左侧缸壁横坐标为 \(0\),右侧缸壁横坐标为 \(n-1\)。鱼缸底部障碍的起伏可以用 \(\{h_n\}\) 描述,其中 \(h_i\) 表示障碍在横坐标为 \(i\) 时高度为 \(h_i\),\(i\sim i+1\) 间障碍的起伏可以看做以 \((i,h_i), (i+1,h_{i+1})\) 为两端的线段。
现在你要支持 \(m\) 次以下两种操作:
- 查询:给定水位高度 \(h_0\),求水淹没的面积(不包括障碍)。
- 修改:给定 \(i, v\),将 \(h_i \gets v\)。
\(n,m \leq10^5\),保证 \(0\leq h_i,h_0,v\leq V=10^3\)。
题目分析
算法一:\(\scriptsize\mathcal{O}\Big((n+m)V\Big)\)
发现 \(V\) 很小,考虑从这里入手。我们考虑维护 \(f_H\) 表示 \(h_0=H\) 时的答案。那么预处理时我们从小到大枚举 \(H\),再依次枚举 \(i\) 统计 \([i, i+1]\) 间部分对 \(f_H\) 的贡献。首先让 \(f_H\) 继承 \(f_{H-1}\) 的答案,然后考虑 \(H\) 增加一对答案的增量。事实上,我们可以不用继承答案,直接计算 \(f_H\),也是可以的。我们记 \(mi=\min\{h_i, h_{i+1}\}\),\(mx=\max\{h_i, h_{i+1}\}\),\(\Delta=|h_{i+1}-h_i|\)。那么我们分三类讨论:
- \(H \leq mi\):
那么此时 \([i, i+1]\) 一点水都没有,没有任何贡献。 - \(H \in (mi, mx]\):
增量为一个梯形,根据相似三角形,上底 \(1\times\frac{H-mi}{\Delta}\),下底 \(1\times\frac{H-1-mi}{\Delta}\),高 \(1\),于是这部分面积为 \(S=\frac{H-mi-\frac{1}{2}}{\Delta}\),令 \(f_H\gets f_H+S\)。 - \(H \gt mx\):
增量即为一个 \(1\times 1\) 的正方形,\(f_H\gets f_H+1\)。
于是,我们可以在 \(\mathcal{O}(nV)\) 的时间内预处理出 \(f\)。询问即可 \(\mathcal{O}(1)\)。考虑修改,发现修改仅需要将原先 \([i, i+1]\) 的贡献在 \(f\) 中删除,然后再将修改后的答案加进去即可,单次修改 \(\mathcal{O}(V)\)。总时间复杂度为 \(\mathcal{O}\Big((n+m) V\Big)\),可以通过。
算法二:\(\scriptsize\mathcal{O}\Big((n+m)\log V\Big)\)
算法一菜飞了,但凡 \(V\) 再大一点就吃不消了,考虑优化。
我们很自然地发现,\([i, i+1]\) 对答案的贡献按照上面的分讨,分为三个部分,我们不考虑增量,而是考虑总贡献:
- 对于 \(H \leq mi\):
没有任何贡献。 - 对于 \(H \in (mi, mx]\):
贡献为一个三角形,竖直边长 \(H-mi\),横边长 \(1\times \frac{H-mi}{\Delta}\),贡献为 \(\frac{(H-mi)^2}{2\Delta} = \frac{1}{2\Delta}H^2 - \frac{mi}{\Delta} H + \frac{mi^2}{2\Delta}\)。 - 对于 \(H \gt mx\):
贡献贡献为一个梯形,拆分成一个三角形和一个矩形。\(S_{\triangle}=\frac{\Delta}{2}\),矩形面积为 \(H-mx\),总贡献为 \(H+\Big(\frac{\Delta}{2}-mx\Big)\)。
读者已经猜到我想要表示什么了,这三种情况可以归约到关于 \(H\) 的二次函数,我们只需要分别维护对应系数之和即可。至于 \(H\) 的限制,使用树状数组即可。
时间复杂度:\(\mathcal{O}\Big((n+m)\log V\Big)\),瓶颈在于树状数组。
代码
算法一:$\scriptsize\mathcal{O}\Big((n+m)V\Big)$
#include <cstdio>
using namespace std;
#define isdigit(x) ('0' <= x && x <= '9')
inline void read(int &x) {
x = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar());
for (; isdigit(ch); ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);
}
inline char read() {
char ch; do ch = getchar(); while (ch != 'Q' && ch != 'U');
return ch;
}
inline int abs(int x) { return x < 0 ? -x : x; }
inline int min(int a, int b) { return a < b ? a : b; }
inline int max(int a, int b) { return a > b ? a : b; }
const int N = 100010, V = 1010;
using ld = long double;
const ld eps = 1e-10;
int n, m, h[N], mii[N], derr[N];
ld ans[V];
signed main() {
read(n), read(m);
for (int i = 1; i <= n; ++i) read(h[i]), mii[i - 1] = min(h[i], h[i - 1]), derr[i - 1] = abs(h[i] - h[i - 1]);
for (int H = 1; H <= 1000; ++H) {
ans[H] = ans[H - 1];
for (int i = 1, mi, der; i + 1 <= n; ++i) {
mi = mii[i];
if (H <= mi) {
} else if (H <= max(h[i], h[i + 1])) {
der = derr[i];
ans[H] += (H - mi - 0.5) / der;
} else {
ans[H] += 1;
}
}
}
for (int H, p, v; m--; ) {
char op = read();
if (op == 'Q') {
read(H);
#ifdef XuYueming
printf(">>> ");
#endif
printf("%.1Lf\n", ans[H] + eps);
} else {
read(p), read(v), ++p;
ld tot = 0;
int mi = mii[p], der = derr[p];
int MI = mii[p - 1], DER = derr[p - 1];
for (int H = 1; H <= 1000; ++H) {
if (p + 1 <= n) {
if (H <= mi) {
} else if (H <= max(h[p], h[p + 1])) {
tot += (H - mi - 0.5) / der;
} else {
tot += 1;
}
}
if (p - 1 >= 1) {
if (H <= MI) {
} else if (H <= max(h[p], h[p - 1])) {
tot += (H - MI - 0.5) / DER;
} else {
tot += 1;
}
}
ans[H] -= tot;
}
h[p] = v;
tot = 0;
mi = mii[p] = min(h[p], h[p + 1]);
der = derr[p] = abs(h[p] - h[p + 1]);
MI = mii[p - 1] = min(h[p - 1], h[p]);
DER = derr[p - 1] = abs(h[p - 1] - h[p]);
for (int H = 1; H <= 1000; ++H) {
if (p + 1 <= n) {
if (H <= mi) {
} else if (H <= max(h[p], h[p + 1])) {
tot += (H - mi - 0.5) / der;
} else {
tot += 1;
}
}
if (p - 1 >= 1) {
if (H <= MI) {
} else if (H <= max(h[p], h[p - 1])) {
tot += (H - MI - 0.5) / DER;
} else {
tot += 1;
}
}
ans[H] += tot;
}
}
}
return 0;
}
算法二:$\scriptsize\mathcal{O}\Big((n+m)\log V\Big)$
#include <cstdio>
#include <iostream>
using namespace std;
const int N = 100010;
const int V = 1005;
using ld = long double;
int n, m, h[N];
struct Bit_Tree {
ld tree[V];
inline void modify(int p, ld v) {
for (; p < V; p += p & -p)
tree[p] += v;
}
inline void modify(int l, int r, ld v) {
modify(l, v), modify(r + 1, -v);
}
inline ld query(int p) {
ld res = 0;
for (; p; p &= p - 1)
res += tree[p];
return res;
}
} t[3];
inline void add(int i, int f) {
int mi = min(h[i], h[i + 1]);
int mx = max(h[i], h[i + 1]);
int delta = mx - mi;
if (mi + 1 <= mx) {
t[2].modify(mi + 1, mx, f * (.5 / delta));
t[1].modify(mi + 1, mx, f * (-1. * mi / delta));
t[0].modify(mi + 1, mx, f * (.5 * mi * mi / delta));
}
t[1].modify(mx + 1, f * 1);
t[0].modify(mx + 1, f * (delta / 2. - mx));
}
inline ld query(int h) {
return t[2].query(h) * h * h + t[1].query(h) * h + t[0].query(h);
}
signed main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
scanf("%d", &h[i]);
for (int i = 1; i < n; ++i)
add(i, 1);
for (int h0, p, v; m--; ) {
char op[2];
scanf("%s", op);
if (*op == 'Q') {
scanf("%d", &h0);
printf("%.1Lf\n", query(h0));
} else {
scanf("%d%d", &p, &v), ++p;
if (p > 1) add(p - 1, -1);
if (p < n) add(p, -1);
h[p] = v;
if (p > 1) add(p - 1, 1);
if (p < n) add(p, 1);
}
}
return 0;
}
卡常后代码
#include <cstdio>
const int MAX = 1 << 26;
char buf[MAX], *inp = buf;
template <typename T>
inline void read(T &x) {
x = 0; char ch = *inp++;
for (; ch < 48; ch = *inp++);
for (; ch >= 48; ch = *inp++) x = (x << 3) + (x << 1) + (ch ^ 48);
}
const int N = 100010;
const int V = 1005;
using ld = long double;
int n, m, h[N];
ld tree[V][3];
inline void add(int i, bool del) {
int mi = h[i] < h[i + 1] ? h[i] : h[i + 1];
int mx = h[i] ^ h[i + 1] ^ mi;
int delta = mx - mi;
if (mi < mx) {
int l = mi + 1, r = mx + 1;
ld v2 = .5 / delta, v1 = -1. * mi / delta, v0 = mi * mi * v2;
if (del) v0 = -v0, v1 = -v1, v2 = -v2;
for (; l < r; l += l & -l) {
tree[l][0] += v0;
tree[l][1] += v1;
tree[l][2] += v2;
}
for (; r < l && r < V; r += r & -r) {
tree[r][0] -= v0;
tree[r][1] -= v1;
tree[r][2] -= v2;
}
for (; l < r; l += l & -l) {
tree[l][0] += v0;
tree[l][1] += v1;
tree[l][2] += v2;
}
}
ld v1 = 1, v0 = delta / 2. - mx;
if (del) v1 = -v1, v0 = -v0;
for (int p = mx + 1; p < V; p += p & -p) {
tree[p][0] += v0;
tree[p][1] += v1;
}
}
inline ld query(int h) {
ld v2 = 0, v1 = 0, v0 = 0;
for (int p = h; p; p &= p - 1) {
v0 += tree[p][0];
v1 += tree[p][1];
v2 += tree[p][2];
}
return v2 * h * h + v1 * h + v0;
}
signed main() {
fread(buf, 1, MAX, stdin), read(n), read(m), read(h[1]);
for (int i = 2; i <= n; ++i)
read(h[i]), add(i - 1, false);
for (int h0, p, v; m--; ) {
char op;
do op = *inp++; while (op != 'Q' && op != 'U');
if (op == 'Q') {
read(h0);
printf("%.1Lf\n", query(h0));
} else {
read(p), read(v), ++p;
if (p > 1) add(p - 1, true);
if (p < n) add(p, true);
h[p] = v;
if (p > 1) add(p - 1, false);
if (p < n) add(p, false);
}
}
return 0;
}
本文作者:XuYueming,转载请注明原文链接:https://www.cnblogs.com/XuYueming/p/18705973。
若未作特殊说明,本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。

浙公网安备 33010602011771号