【题目描述】
在无聊的时候,小 $K$ 和小 $H$ 会在纸上玩这样一个游戏。
我们可以将纸看做一个平面直角坐标系。小 $H$ 会先在上面画出 $n$ 个圆,并把每个圆的圆心以及半径都告诉小 $K$。小 $H$ 画的 $n$ 个圆中,任意两个圆不会出现相交或相切的情况。小 $K$ 需要做的就是从这 $n$ 个圆中选出若干个圆,使得选出的任意一个圆都不被另一个选出的圆包含。游戏的目标就是要选出尽量多的圆。
游戏一次一次进行着,小 $K$ 已经对游戏的规则感到了厌倦,所以他决定修改游戏的规则。对于第 $i$ 个圆,我们定义它的价值为 $w_i$ 。新的游戏目标是使得选出的圆价值和最大(不一定数量最多)。但是圆圈可能很多,或者圆圈的分布非常奇怪,或者小 $K$ 还有别的事情要做。所以他只好拜托你来帮他求出这个最大值了。
【输入格式】
第一行一个整数 $n$ 表示圆圈的个数。
接下来 $n$ 行每行 $4$ 个整数 $x_i,y_i,r_i$ 和 $w_i$ ,分别表示第 $i$ 个圆的圆心横坐标、纵坐标、半径,和价值。
【输出格式】
输出一行,包含一个整数,代表选出的圆的最大价值。
【样例】
样例输入
3
3 4 2 3
6 4 7 5
9 4 1 4
样例输出
7
样例解释
如果选择价值最大的圆 $2$ ,可以获得的价值和为 $5$。如果选择圆 $1$ 和圆 $3$,虽然它们的单个价值都不是最大的,但价值和可以达到 $3+ 4 = 7$。
【数据范围与提示】
测试点编号 | $n=$ |
$1$ | $1$ |
$2$ | $2$ |
$3$ | $3$ |
$4$ | $4$ |
$5$ | $8$ |
$6$ | $12$ |
$7$ | $16$ |
$8,9$ | $1000$ |
$10,11$ | $2000$ |
$12,13$ | $3000$ |
$14,15$ | $5000$ |
$16,17$ | $60000$ |
$18,19$ | $70000$ |
$20,21$ | $80000$ |
$22,23$ | $90000$ |
$24,25$ | $100000$ |
对于全部测试数据 $1 le x_i,y_i,r_i le 10^8,1 le w_i le 1000 $。
保证不存在相交或相切的两个圆。
【题解】
看到不互相包含的圆,显然是最大权独立集问题。又注意到每个圆一定被另一个最小圆包含,因此圆与圆之间由包含关系构成树形结构。若能够建出树则可以树形 $dp$。
考虑 $60$ 分做法。按 $r$ 从小到大扫一遍,对于每个圆找到最小的包含它的圆,直接建边跑树形 $dp$ 即可。效率 $O(n^2)$。
考虑如何优化建树。首先可以反过来建边,从外往里。同样从小到大插入每个圆。由于圆与圆不相交,圆心在当前插入圆内的圆一定被当前圆包含。直接连边然后删除即可。
由于是二维限制,用 $kd-tree$ 或二维线段树均可。
另一种做法是用扫描线,对于每个圆拆成上下两半,左右两个端点视为插入与删除的扫描线。用 $set,splay$ 等数据结构维护每个半圆的位置上下关系。查询时查询上半圆前驱与下半圆后继即可。
【代码】
#include<bits/stdc++.h> inline int read ( void ) { int x=0;char ch;bool f=true; while ( !isdigit(ch=getchar()) ) if ( ch=='-' ) f=false; for ( x=ch^48;isdigit(ch=getchar()); ) x=(x<<1)+(x<<3)+(ch^48); return f ? x : -x ; } int f[100010],n,tot,root; struct tree { int ch[4],tot,id; } t[10000010]; struct Circle { long long x,y,r;int w; } C[100010]; std::vector<int> E[100010]; const int inf=1000000000; const int L=-inf,R=inf; inline bool inside ( int l,int r,int d,int u,int x,int y,long long R ) { long long dx=std::max(std::max(x-r,l-x),0),dy=std::max(std::max(y-u,d-y),0); return (dx*dx+dy*dy<=R*R); } inline void pushup ( int k ) { t[k].tot=t[t[k].ch[0]].tot+t[t[k].ch[1]].tot+t[t[k].ch[2]].tot+t[t[k].ch[3]].tot; } inline int query ( int l,int r,int d,int u,int k,int x,int y,int R ) { if ( !t[k].tot or !inside(l,r,d,u,x,y,R) ) return 0; if ( l==r and d==u ) { t[k].tot=0;return f[t[k].id]; } int mid1=(l+r)>>1,mid2=(d+u)>>1,res=0; res+=query(l,mid1,d,mid2,t[k].ch[0],x,y,R); res+=query(l,mid1,mid2+1,u,t[k].ch[1],x,y,R); res+=query(mid1+1,r,d,mid2,t[k].ch[2],x,y,R); res+=query(mid1+1,r,mid2+1,u,t[k].ch[3],x,y,R); pushup(k); return res; } inline void modify ( int l,int r,int d,int u,int &k,int x,int y,int i ) { if ( !k ) k=++tot; if ( l==r and d==u ) { t[k].tot=1;t[k].id=i;return; } int mid1=(l+r)>>1,mid2=(d+u)>>1; if ( x<=mid1 and y<=mid2 ) modify(l,mid1,d,mid2,t[k].ch[0],x,y,i); if ( x<=mid1 and y>mid2 ) modify(l,mid1,mid2+1,u,t[k].ch[1],x,y,i); if ( x>mid1 and y<=mid2 ) modify(mid1+1,r,d,mid2,t[k].ch[2],x,y,i); if ( x>mid1 and y>mid2 ) modify(mid1+1,r,mid2+1,u,t[k].ch[3],x,y,i); pushup(k); } signed main() { n=read(); for ( int i=1;i<=n;i++ ) C[i].x=read(),C[i].y=read(),C[i].r=read(),C[i].w=read(); ++n;C[n].r=inf;std::sort(C+1,C+n+1,[&](const Circle &c1,const Circle &c2){return c1.r<c2.r;}); for ( int i=1;i<=n;i++ ) f[i]=std::max(query(L,R,L,R,root,C[i].x,C[i].y,C[i].r),C[i].w),modify(L,R,L,R,root,C[i].x,C[i].y,i); return !printf("%d ",f[n]); }