题目描述
WJJ喜欢“魔兽争霸”这个游戏。在游戏中,巫妖是一种强大的英雄,它的技能Frozen Nova每次可以杀死一个小精灵。我们认为,巫妖和小精灵都可以看成是平面上的点。
当巫妖和小精灵之间的直线距离不超过R,且巫妖看到小精灵的视线没有被树木阻挡(也就是说,巫妖和小精灵的连线与任何树木都没有公共点)的话,巫妖就可以瞬间杀灭一个小精灵。
在森林里有N个巫妖,每个巫妖释放Frozen Nova之后,都需要等待一段时间,才能再次施放。不同的巫妖有不同的等待时间和施法范围,但相同的是,每次施放都可以杀死一个小精灵。
现在巫妖的头目想知道,若从0时刻开始计算,至少需要花费多少时间,可以杀死所有的小精灵?
输入
输入文件第一行包含三个整数N、M、K(N,M,K<=200),分别代表巫妖的数量、小精灵的数量和树木的数量。
接下来N行,每行包含四个整数x, y, r, t,分别代表了每个巫妖的坐标、攻击范围和施法间隔(单位为秒)。
再接下来M行,每行两个整数x, y,分别代表了每个小精灵的坐标。
再接下来K行,每行三个整数x, y, r,分别代表了每个树木的坐标。
输入数据中所有坐标范围绝对值不超过10000,半径和施法间隔不超过20000。
输出
输出一行,为消灭所有小精灵的最短时间(以秒计算)。如果永远无法消灭所有的小精灵,则输出-1。
样例输入
2 3 1 -100 0 100 3 100 0 100 5 -100 -10 100 10 110 11 5 5 10
样例输出
5
题解
这道题思路不是很难,但是实现起来有点繁,容易错。
先读题,有巫妖、精灵和树三类角色,都有确定的位置。巫妖能对一定范围内的精灵造成一击必杀,但是要满足两个条件:巫妖与精灵的距离小于巫妖的射程、巫妖与精灵的连线没有与任何一个树(一个圆)相交,我个人觉得相切是OK的(原来的数据也没有卡这个,可能是因为不好判断,也没有准确的判断)。同时巫妖有CD(CD:技能冷却。注意t=0时巫妖可以直接发出攻击),原题问最少需要多少时间能击杀所有精灵,或者能够知道根本无法全部击杀。
可以很容易的知道,这道题的一个难点是找出一个巫妖能够对哪些精灵进行攻击。不妨枚举所有的巫妖,然后对于每一个巫妖,枚举每一个精灵。首先计算出巫妖和精灵的距离rs。如果此时距离已经大于攻击范围,就直接continue。否则就需要判断是否有树阻挡了。
如何判断树是否有影响?
如图所示,A为巫妖的位置,B为精灵的位置,C为圆心,是树的位置。现在过圆心C作线段AB垂线,垂足为H 。如果说树能够造成阻挡,那么分为两种情况:一种是H在线段AB上,如上图。显然直接判断CH的长度是否大于圆的半径即可。我们假设几个变量:直线AB方程为y=k1*x+b1,直线CH方程为y=k2*x+b2,点H的坐标为(x1,y1),其余变量用如XA,YB来表示已知点的横纵坐标。已知A、B两点的坐标,可以在枚举树之前就预处理出直线AB的解析式,显然k1、b1已知。由于ABCH,所以k1*k2=-1,通过这个关系式求出k2。由于C点坐标已知,就可以得到直线CH的解析式,b2已知。因此可以联立方程解出点H的坐标(x1,y1)。就代码层面来说都是O(1)的,代码中的geometry(几何)函数有求解的过程,以供参考。求出H坐标后,由于H在线段AB上,此刻一定满足H的横坐标一定在A、B之间(A、B左右关系不明确,可用max、min函数来判断。以上对于纵坐标同样成立)。这时用两点坐标公式求出线段CH的长度,与半径比大小。如果大于等于半径,说明树无法造成阻挡,反之即可。
当然也可能出现垂足不在线段上的情况(毕竟不是直线),对于这第二种情况,判断方式就是H的横坐标不在A、B之间。这样直接找AC、BC中较小的一个,判断与半径的大小关系。如果最小的线段都比半径大,说明树根本阻拦不到,否则树可以阻拦。
通过以上步骤,可以确定哪一个巫妖可以攻击哪些精灵。如果有精灵无法被任意一个巫妖攻击到,那么输出-1无解。
建议了解网络流的基本知识以及基本代码再阅读下面的内容。
如果所有精灵都能被攻击到,那么只用计算最小时间。翻译一下题目,就是:每一个巫妖可以对指定的精灵造成攻击力为1点的伤害(一击致命),每一个精灵也只能承受一点攻击力。如果假设每一个精灵的殒命都能对一个“英雄”提供一点经验点,那么当经验点为精灵数时,代表所有精灵都被OK掉,此时的时间为答案。于是可以用网络流,由于在满足条件的情况下,一定能找到从源点流向汇点的最大流为精灵数,唯一需要考虑的是能不能在某一个时间内攻击到所有精灵(正是因此才要二分)。首先建立源点通向每一个巫妖,边权为每一个巫妖能够攻击的精灵数,表示总攻击力(放心,根据后面建的边,不会攻击到其他的精灵,所以总攻击力可能用完,也可能有剩余),然后根据巫妖与精灵的相互关系(从巫妖指向精灵)再建部分边,边权为1,表示一点攻击力。最后让所有精灵都指向汇点,边权还是1,表示对“英雄”提供的经验值(假设的,方便理解)。
因此完全可以二分总时间,直接判断出每一个巫妖能流出多少攻击力,再判断杀死的精灵数是否是所有的精灵数。由于求的是最短时间,因此满足条件更新右端点,否则更新左端点。枚举好时间之后就可以建边了。注意t=0时也可以攻击一次,所以建的巫妖的边权要+1。然后直接最大流,然后所有巫妖用尽洪荒之力能否在规定时间内搞定所有精灵。注意,之所以选择网络流,是因为这是以一种比较优的效率求出最优的路线,来求出可得的最大流。具体的写法是由一个bfs、一个dfs解决,此时就不赘述了。bfs找路径,dfs搜最大流。注意建反边时边权初始为0。
参考代码
#include<queue>
#include<cstdio>
#include<cstring>
#define EXP 0.0000001
#define INF 999999999
using namespace std;
struct tree
{
int nxt,to,dis;
}tr[120001];
int head[120001],cnt=-1;
struct pos1
{
double x,y,r;int t;
}a[2001];
struct pos2
{
double x,y;
}b[2001];
struct pos3
{
double x,y,r;
}c[2001];
queue<int>q;int dis[120001];
int n,m,k,lis[2001][2001],nus=0,mx=0;
double max_d(double p,double q) { return p-q>EXP?p:q; }
double min_d(double p,double q) { return p-q<EXP?p:q; }
int max1(int p,int q) { return p>q?p:q; }
int min1(int p,int q) { return p<q?p:q; }
void geometry()
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
int pd=0;double k1,b1,rs,k2,b2,x1,y1;
k1=(a[i].y-b[j].y)/(a[i].x-b[j].x);
b1=a[i].y-k1*a[i].x;
rs=(a[i].x-b[j].x)*(a[i].x-b[j].x)+
(a[i].y-b[j].y)*(a[i].y-b[j].y);
if(rs-a[i].r*a[i].r>EXP) continue;
k2=-1.0/k1;
for(int z=1;z<=k;z++)
{
b2=c[z].y-k2*c[z].x;
x1=(b2-b1)/(k1-k2);
y1=k1*x1+b1;
if(y1-max_d(a[i].y,b[j].y)>EXP||y1-min_d(a[i].y,b[j].y)<EXP)
{
double r1,r2;
r1=(a[i].x-c[z].x)*(a[i].x-c[z].x)+(a[i].y-c[z].y)*(a[i].y-c[z].y);
r2=(b[j].x-c[z].x)*(b[j].x-c[z].x)+(b[j].y-c[z].y)*(b[j].y-c[z].y);
if(min_d(r1,r2)-c[z].r*c[z].r<EXP) pd=1;
}
else
{
double rt;
rt=(c[z].x-x1)*(c[z].x-x1)+(c[z].y-y1)*(c[z].y-y1);
if(rt-c[z].r*c[z].r<EXP) pd=1;
}
if(pd==1) break;
}
if(pd==0)
{
lis[j][++lis[j][0]]=i;
if(lis[j][0]==1) nus--;
}
}
}
}
void build_tree(int u,int v,int d)
{
tr[++cnt].nxt=head[u];
tr[cnt].to=v;
tr[cnt].dis=d;
head[u]=cnt;
tr[++cnt].nxt=head[v];
tr[cnt].to=u;
tr[cnt].dis=0;
head[v]=cnt;
}
void init(int x)
{
for(int i=1;i<=n;i++) build_tree(m+n+2,i,x/a[i].t+1);
for(int i=1;i<=m;i++) build_tree(i+n,m+n+1,1);
for(int i=1;i<=m;i++)
for(int j=1;j<=lis[i][0];j++)
build_tree(lis[i][j],i+n,1);
}
int bfs()
{
memset(dis,-1,sizeof(dis));
dis[m+n+2]=0;
q.push(m+n+2);
while(!q.empty())
{
int pt=q.front();q.pop();
for(int i=head[pt];i;i=tr[i].nxt)
{
int to=tr[i].to;
if(dis[to]==-1&&tr[i].dis)
{
dis[to]=dis[pt]+1;
q.push(to);
}
}
}
return dis[m+n+1]!=-1;
}
int dfs(int k,int flow)
{
if(k==m+n+1) return flow;
int delta=flow;
for(int i=head[k];i;i=tr[i].nxt)
{
int to=tr[i].to;
if(dis[to]==(dis[k]+1)&&(tr[i].dis)>0&&delta>0)
{
int d=dfs(to,min1(tr[i].dis,delta));
tr[i].dis-=d;
delta-=d;tr[i^1].dis+=d;
}
}
if(delta==flow) dis[k]=-1;
return flow-delta;
}
int dinic()
{
int ans=0;
while(bfs()) ans+=dfs(m+n+2,INF);
return ans;
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)
{
scanf("%lf%lf%lf%d",&a[i].x,&a[i].y,&a[i].r,&a[i].t);
mx=max1(mx,a[i].t);
}
for(int i=1;i<=m;i++)
scanf("%lf%lf",&b[i].x,&b[i].y);
for(int i=1;i<=k;i++)
scanf("%lf%lf%lf",&c[i].x,&c[i].y,&c[i].r);
nus=m;
geometry();
if(nus)
{
printf("-1");
return 0;
}
int l=0,r=mx*m;
while(l<r)
{
int mid=(l+r)/2;cnt=1;
memset(head,0,sizeof(head));
init(mid);
if(dinic()==m) r=mid;
else l=mid+1;
}
printf("%d",l);
return 0;
}