测试地址:Circles Game
题目大意:给定个圆,圆两两之间只可能有相离或包含两种关系。两个人博弈,每次可以取走一个圆以及被这个圆包含的所有圆,不能取的人输,问先手必胜还是必败。
做法:本题需要用到扫描线+set+树上删边博弈。
两个圆之间只可能相离或包含,那么显然这种包含关系能够构成一座森林的形状。如果再将所有不被任何圆包含的圆与一个空点相连,这就是一棵树了。意识到我们每次取走一个圆,在树中的体现就是切掉这个点的父边,把它的子树删掉。那么问题就变成不断删边,最后只剩下根节点时结束。这种博弈类型就是经典的树上删边博弈。
这种树上删边博弈是一个公平的组合游戏,因此可以用SG定理进行推导。我们首先发现,一条链的SG值肯定是这条链中的边数。如果树的形态是从根连出几条链的形式,这显然就是一个Nim游戏,所以这样的树的SG值为它的所有连出的链的边数的异或和。因为SG值相同,所以这种树也就等价于一条长度与其SG值对应的链。就这么一直推导下去,就可以推导出某一棵树的SG值了。这样我们就可以判断先手必胜还是必败了。
那么问题是不是就解决了呢?还没有。注意到一开始连边的过程中,我们需要找到包含一个圆的最小的圆,如果暴力找的话,时间复杂度最坏是的,无法接受(但这道题好像数据出水了可以过)。所以我们需要运用另一种方法:扫描线。
一条垂直于轴的扫描线从左往右扫描,因为圆与圆之间不会相交,所以圆与扫描线的交点的高低关系是不变的,因此我们用set维护圆与扫描线的交点。将一个圆拆分为两个事件:加入事件和删除事件。在加入一个圆时,我们需要求出包含它的最小圆的编号。在加入圆时,它与扫描线相切,于是我们在set里查找离它最近的两个交点,然后分类讨论:
1.如果上下有某一个方向找不到交点,表示当前圆在最外层。
2.如果上下交点属于同一个圆,则这个圆就是所求的最小包含圆。
3.如果上下交点不属于同一个圆,但属于两个同层的圆(层定义为从内到外最少需要经过的圆数,其实也就是在树中从根到该点的距离,可以在算法中顺便求出),那么这两个圆和当前圆被一个圆共同包含,这个圆就是这两个圆的父亲,它就是所求的最小包含圆。
4.如果上下交点属于两个不同层的圆,那么层数较大的圆和当前圆共同包含于层数较小的圆,则这个层数较小的圆为所求的最小包含圆。
于是我们就讨论完了所有的情况,这样就可以完成连边的操作了。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int T,n,f[20010],F[20010],fa[20010],first[20010]={0},tot=0;
ll x[20010],y[20010],r[20010],now=0;
struct event
{
bool type;
int id,val;
}op[50010];
struct edge
{
int v,next;
}e[20010];
double calc(int id,bool type)
{
double R=(double)r[id],A=fabs((double)(x[id]-now)),Y=(double)y[id];
if (!type) return Y-sqrt(R*R-A*A);
else return Y+sqrt(R*R-A*A);
}
struct point
{
int id;
bool type;
bool operator < (point a) const
{
if (id==a.id) return type<a.type;
double ans1=calc(id,type),ans2=calc(a.id,a.type);
return ans1<ans2;
}
};
set<point> S;
set<point>::iterator it;
ll dis(ll x1,ll y1,ll x2,ll y2)
{
return (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);
}
bool cmp(event a,event b)
{
if (a.val==b.val) return a.type>b.type;
else return a.val<b.val;
}
void insert(int a,int b)
{
e[++tot].v=b;
e[tot].next=first[a];
first[a]=tot;
}
void dfs(int v)
{
F[v]=0;
for(int i=first[v];i;i=e[i].next)
{
dfs(e[i].v);
F[v]^=(F[e[i].v]+1);
}
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld%lld%lld",&x[i],&y[i],&r[i]);
int a=(i<<1)-1,b=(i<<1);
op[a].id=i,op[a].val=x[i]-r[i],op[a].type=0;
op[b].id=i,op[b].val=x[i]+r[i],op[b].type=1;
}
sort(op+1,op+(n<<1)+1,cmp);
point nxt,up,down;
x[0]=y[0]=0;r[0]=100000;
S.clear();
nxt.id=0,nxt.type=0;S.insert(nxt);
nxt.id=0,nxt.type=1,S.insert(nxt);
f[0]=-1;
for(int i=1;i<=(n<<1);i++)
{
now=op[i].val;
if (!op[i].type)
{
nxt.id=op[i].id,nxt.type=0;
it=S.lower_bound(nxt);
up=(*it);
it--;
down=(*it);
if (up.id==down.id)
{
f[op[i].id]=f[up.id]+1;
fa[op[i].id]=up.id;
}
else if (f[up.id]==f[down.id])
{
f[op[i].id]=f[up.id];
fa[op[i].id]=fa[up.id];
}
else
{
if (f[up.id]>f[down.id])
swap(up,down);
f[op[i].id]=f[up.id]+1;
fa[op[i].id]=up.id;
}
S.insert(nxt);
nxt.type=1;
S.insert(nxt);
}
else
{
nxt.id=op[i].id,nxt.type=0;
S.erase(nxt);
nxt.type=1;
S.erase(nxt);
}
}
memset(first,0,sizeof(first));
tot=0;
for(int i=1;i<=n;i++)
insert(fa[i],i);
dfs(0);
printf("%s
",F[0]?"Alice":"Bob");
}
return 0;
}