BZOJ 1822 浅谈计算几何在网络流建模中的实际运用

这里写图片描述
世界真的很大
网络流是个神奇东西,可以用来解决很多意想不到的问题
但是做题久了,有套路了,大多数网络流也就不是那么难
只要建好模型,跑模板就OK
然后就会出现某些丧心病狂的出题人想到干脆卡一下建边之类的东西,,,
这道题就是如此
网络流建模还是很好想,给人一种这道题很好做的错觉
但是问题往往没有那么简单
且先看题目:
description

WJJ喜欢“魔兽争霸”这个游戏。在游戏中,巫妖是一种强大的英雄,它的技能Frozen Nova每次可以杀死一个小精灵。我们认为,巫妖和小精灵都可以看成是平面上的点。 当巫妖和小精灵之间的直线距离不超过R,且巫妖看到小精灵的视线没有被树木阻挡(也就是说,巫妖和小精灵的连线与任何树木都没有公共点)的话,巫妖就可以瞬间杀灭一个小精灵。 在森林里有N个巫妖,每个巫妖释放Frozen Nova之后,都需要等待一段时间,才能再次施放。不同的巫妖有不同的等待时间和施法范围,但相同的是,每次施放都可以杀死一个小精灵。 现在巫妖的头目想知道,若从0时刻开始计算,至少需要花费多少时间,可以杀死所有的小精灵?

input

输入文件第一行包含三个整数N、M、K(N,M,K<=200),分别代表巫妖的数量、小精灵的数量和树木的数量。 接下来N行,每行包含四个整数x, y, r, t,分别代表了每个巫妖的坐标、攻击范围和施法间隔(单位为秒)。 再接下来M行,每行两个整数x, y,分别代表了每个小精灵的坐标。 再接下来K行,每行三个整数x, y, r,分别代表了每个树木的坐标。 输入数据中所有坐标范围绝对值不超过10000,半径和施法间隔不超过20000

output

输出一行,为消灭所有小精灵的最短时间(以秒计算)。如果永远无法消灭所有的小精灵,则输出-1。

题目描述还是很有意思的
虽然没玩过魔兽233
因为求最小所以考虑二分
二分出最少需要的时间
很容易想到网络流模型,
源点向所有巫妖连边,边权是巫妖在二分的时间内的攻击次数
所有小精灵向汇点连边,边权为一,表示小精灵只有一个
所有巫妖向其能攻击到的小精灵连边,边权INF
每次check时跑最大流,看最大流是否等于小精灵的数量
乍一看好像就是这样,其实也就是这样。。
但问题往往没有那么简单
难点在于判断,判断巫妖A是否能攻击到小精灵B,而不被之间的树挡住
首先如果A离B的距离比A的攻击范围大那绝对是不可能了
然后是判断是否中间有树挡住
枚举每棵树,看树的圆心到 线段AB 的距离是否大于树的半径。但因为是线段所以和普通的点到直线有点不同。过圆心C作AB的垂线,如果交点在线段AB上,那AB到圆心的距离就是垂线段的长度,但如果交点在AB的延长线上,圆心到线段AB的距离就是圆心到A与B之间较短的那一个
关键是怎么判断交点究竟是在线段上还是在线段的延长线上
这里首先用到的是高中数学里就会学到的向量的点积
考虑线段的两端A,B,如果AB都在C的某一侧,那C的垂线段与AB的交点就在AB的延长线上,而此时,ACB这个角是一个锐角,换句话说就是向量CA,CB的夹角是锐角,也就是说CA,CB的点积的值为正,反之A,B就各在C的两侧,而此时C的垂线段必然落在AB上
针对第一种情况,直接两点之间距离公式算出距离就好,如果距离比树的半径小的话就会被树挡住
针对第二种情况,就需要计算点到直线的距离
听说确是有一个什么点到直线的距离公式,但是太low了而且函数解析式不好处理
这里用到的是向量的叉积
两向量之叉积得到的是两向量之间平行四边形的面积,其一半就是三角形的面积,那就可以很快得出ABC这个三角形的面积,除之以AB之长,就得到了C到AB的距离,判断这个距离和树的半径大小,就知道会不会被挡住了
然后因为代码量偏大,所以细节方面有几点注意
因为每次二分check只是对于部分边的修改且不还原,所以每次还是重新建边的为好,为了时间复杂度,还是不要每次判断小精灵能否被打倒为好,用一个二维数组mp[i][j]保存巫妖i能否打倒小精灵j
因为在0时刻每个巫妖就可以发动一次攻击了,所以时间T内巫妖i的最多攻击次数为T/cd[i]+1,cd是指i的冷却时间
其实看到计算几何的话向量的点积叉积是基本的,把解析几何的思路换过来,灵活运用向量是个少走弯路的办法,之后一定要注意
二分答案时考虑边界一定要仔细,什么时候是0,什么时候是1,一定想清楚,因为这道题是从0时刻开始的,所以二分的左边界是0不是1,卡了我好久
另外叉积算出来的面积其实是个向量,用绝对值去掉其符号就是标量了
完整代码:

#include<stdio.h>
#include<cstring>
#include<math.h>
#include<queue>
#include<algorithm>
using namespace std;

const int INF=0x3f3f3f3f;

struct edge
{
    int v,u,last,w;
}ed[500010];

struct point
{
    int x,y;
}pos[100010],loc[100010],sit[100010];

queue <int> state;

int head[100010],dis[100010],book[100010],r[100010],tr[100010],cd[100010],mp[1010][1010];
int num=1,n,m,K,tot=0,S=0,T,big=0;

void add(int u,int v,int w)
{
    num++;
    ed[num].v=v;
    ed[num].w=w;
    ed[num].u=u;
    ed[num].last=head[u];
    head[u]=num;
}

point gvc(point a,point b)
{
    point tmp;
    tmp.x=a.x-b.x;
    tmp.y=a.y-b.y;
    return tmp;
}

double dot(point a,point b)
{
    return a.x*b.x+a.y*b.y;
}

int crs(point a,point b)
{
    return a.x*b.y-a.y*b.x;
}

double dit(point a,point b)
{
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}

double dit(point a,point b,point c)
{
    if(dot(gvc(a,c),gvc(b,c))>0) return min(dit(a,c),dit(b,c));
    return fabs(crs(gvc(c,a),gvc(b,a))/dit(a,b));
}

bool judge(int a,int b)
{
    if(dit(pos[a],loc[b])>r[a]) return false ;
    for(int i=1;i<=K;i++)
        if(dit(pos[a],loc[b],sit[i])<tr[i]) return false ;
    return true ;
}

bool bfs()
{
    while(state.size()) state.pop();
    memset(dis,-1,sizeof(dis));
    dis[S]=0;
    state.push(S);
    while(!state.empty())
    {
        int u=state.front();
        state.pop();
        for(int i=head[u];i;i=ed[i].last)
        {
            int v=ed[i].v;
            if(dis[v]==-1&&ed[i].w>0)
            {
                dis[v]=dis[u]+1;
                state.push(v);
            }
        }
    }
    if(dis[T]==-1) return 0;
    return 1;
}

int dfs(int u,int low)
{
    if(u==T || low==0) return low;
    int a=0;
    for(int i=head[u];i;i=ed[i].last)
    {
        int v=ed[i].v;
        if(ed[i].w>0&&dis[v]==dis[u]+1)
        {
            int tmp=dfs(v,min(low,ed[i].w));
            ed[i].w-=tmp;
            ed[i^1].w+=tmp;
            a+=tmp,low-=tmp;
            if(low==0) return a;
        }
    }
    if(low) dis[u]=-1;
    return a;
}

bool check(int mid)
{
    int bns=0;
    while(bfs())
        bns+=dfs(S,INF);        
    if(bns==m) return true;
    return false ;
}

void recreat(int mid)
{
    num=1;
    for(int i=1;i<=num;i++) ed[i].w=0;
    memset(head,0,sizeof(head));
    for(int i=1;i<=n;i++)
    {
        add(S,i,mid/cd[i]+1);
        add(i,S,0);
    }
    for(int i=1;i<=m;i++)
    {
        add(i+n,T,1);
        add(T,i+n,0);       
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(mp[i][j])
            {
                add(i,j+n,INF);
                add(j+n,i,0);
            }
}

int main()
{
    scanf("%d%d%d",&n,&m,&K);
    T=n+m+1;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d%d%d",&pos[i].x,&pos[i].y,&r[i],&cd[i]);
        big=max(big,cd[i]);
    }
    for(int i=1;i<=m;i++)
        scanf("%d%d",&loc[i].x,&loc[i].y);
    for(int i=1;i<=K;i++)
        scanf("%d%d%d",&sit[i].x,&sit[i].y,&tr[i]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        if(judge(i,j))
        {
            mp[i][j]=1;
            book[j]++;
        }
    for(int i=1;i<=m;i++)
    if(!book[i])
    {
        printf("-1");
        return 0;
    }
    int lf=0,rg=INF,ans=0;
    while(lf<=rg)
    {
        int mid=(lf+rg)>>1;
        recreat(mid);
        if(check(mid))
        {
            ans=mid;
            rg=mid-1;
        }
        else lf=mid+1;
    }
    printf("%d",ans);
    return 0;
}
/*
Whoso pulleth out this sword from this stone and anvil is duly born King of all England
*/

嗯,就是这样

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值