题意
平面直角坐标系上有\(n\)个圆,这些圆互不相交或相切(也就是说,两个圆之间要么相离,要么是包含与被包含的关系)
每个圆有一个权值,请选择若干个圆,使得这些圆两两之间不存在包含或被包含的关系,并且所选择圆的权值和最大
解法
由于圆之间只存在包含与被包含的关系,很容易抽象出一颗树:一个圆在树上结点的父亲时包含它的半径最小的圆
因此有一个\(O(n^2)\)的做法:暴力找到每个圆的父亲,做树形\(DP\)即可。这个树形\(DP\)也很好想,因为树中同一层的结点都是兄弟结点,是不会相互影响的,所以直接把所有儿子的答案与父亲的权值比较转移即可
可以注意到复杂度瓶颈是找父亲的过程,考虑用set优化
由于圆之间只有包含与被包含的关系,我们可以发现这样一个性质:
对于一个圆,我们把它拆成上半部与下半部:从上半部出发向上走,遇到的第一个如果是上半圆,那么它所代表的整圆即是当前该圆的父亲;如果是下半圆,则是当前该圆的兄弟
我们维护一根扫描线,从左扫到右:如果扫到了一个圆的左端点,将其加入一个set中;如果扫到了某个右端点,就把它从set中删除
至于找父亲,set维护y坐标,找到当前圆上半圆的前驱,判断是一个上半圆还是下半圆从而就确定了是当前圆的父亲还是兄弟
连边后树形\(DP\)即可
代码
#include <set>
#include <cmath>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e6 + 10;
int read();
int t;
int fa[N], val[N];
int cap;
int head[N], to[N], nxt[N];
inline void add(int x, int y) { to[++cap] = y, nxt[cap] = head[x], head[x] = cap; }
struct semi {
double x, y, r;
int k, id;
double calc() const { return y - 1.000000 * k * sqrt(r * r - (x - t) * (x - t)); }
bool operator < (const semi& rhs) const {
return (id == rhs.id) ? (k > rhs.k) : (calc() < rhs.calc());
}
} c[N];
bool cmp(const semi& u, const semi& v) { return u.x + u.k * u.r < v.x + v.k * v.r; }
set<semi> s;
typedef set<semi>::iterator iter;
int DFS(int x) {
int res = 0;
for (int i = head[x]; i; i = nxt[i])
if (to[i] != fa[x]) res += DFS(to[i]);
return max(res, val[x]);
}
int main() {
int n = read();
for (int i = 1; i <= n; ++i) {
int u = i * 2, v = i * 2 - 1;
c[u].x = read(), c[u].y = read(), c[u].r = read();
c[v] = c[u];
c[u].k = 1, c[v].k = -1;
c[u].id = c[v].id = i;
val[i] = read();
}
sort(c + 1, c + n * 2 + 1, cmp);
semi ins, del;
for (int i = 1; i <= 2 * n; ++i) {
t = c[i].x + c[i].k * c[i].r;
if (c[i].k == -1) {
iter it = s.upper_bound(c[i]);
if (it != s.end())
fa[c[i].id] = (~(it -> k)) ? fa[it -> id] : it -> id;
ins = c[i], ins.k = 1;
s.insert(c[i]), s.insert(ins);
} else {
del = c[i], del.k = -1;
s.erase(c[i]), s.erase(del);
}
}
for (int i = 1; i <= n; ++i) add(fa[i], i);
int ans = 0;
for (int i = 1; i <= n; ++i)
if (!fa[i]) ans += DFS(i);
printf("%d\n", ans);
return 0;
}
int read() {
int x = 0, c = getchar();
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x;
}