[Luogu P4048] [JSOI2010]冷冻波

本文解析了一道洛谷上的游戏算法题,涉及巫妖击杀小精灵的问题。通过计算几何和网络流算法解决巫妖施法顺序及所需最短时间,并讨论了卡时间与精度的技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

洛谷传送门

题目描述

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

解题分析:

首先我们可以 O(N3) O ( N 3 ) 预处理每个巫妖能够打到的精灵数量,若有精灵没有巫妖可以打到直接输出-1。 再建立超级源点、汇点, 将巫妖与源点相连, 将精灵与汇点相连,,跑Dinic即可。

代码如下:

#include <cstdio>
#include <cctype>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdlib>
#include <iomanip>
#include <queue>
#include <vector>
#define R register
#define W while
#define IN inline
#define gc getchar()  
namespace Geometry
{
    bool fu;
    IN void in (int &x)
    {
        fu = false;
        x = 0; static char c = gc;
        W (!isdigit(c)) 
        {
            if(c == '-') fu = true;
            c = gc;
        }
        W (isdigit(c))
        {
            x = (x << 1) + (x << 3), x += c - 48, c = gc;
        }
        if(fu) x = -x;
    }
    #define db float
    #define EPS 1e-10
    struct pt 
    {
        int x, y, r;
    };
    int man_num, tar_num, tree_num;
    pt tree[205], man[205], tar[205];
    int halt[205], mx_halt;
    bool vis[205];
    IN pt operator + (const pt &x, const pt &y){return (pt){x.x + y.x, x.y + y.y};}
    IN pt operator - (const pt &x, const pt &y){return (pt){x.x - y.x, x.y - y.y};}
    IN db operator * (const pt &x, const pt &y){return x.x * y.y - x.y * y.x;}
    IN float dist (const pt &x){return sqrt(x.x * x.x + x.y * x.y);}
    IN bool is_in (const pt &it, const pt &tree){return dist(it - tree) - tree.r < -EPS;}
}
namespace Max_flow
{
    using std::vector;
    using std::min;
    using std::queue;
    using namespace Geometry;
    #define INF 0x7fffff
    struct Edge
    {
        int to, flow;
    }edge[100005];
    int cnt = -1;
    int layer[405];
    vector <int> nextt[405];// more than 200 indicates the tars
    //402 -> start ; 403 ->end;
    IN void addedge(int from, int to)
    {
        nextt[from].push_back(++cnt);
        edge[cnt].to = to;
        nextt[to].push_back(++cnt);
        edge[cnt].to = from;
    }
    IN bool BFS(const int &start, const int &endd)
    {
        memset(layer, 0, sizeof(layer));
        layer[start] = 1;
        queue <int> q;
        q.push(start);
        R int now;
        W (!q.empty())
        {
            now = q.front();
            q.pop();
            for (R int i = nextt[now].size() - 1; i >= 0; --i)
            {
                if(!edge[nextt[now][i]].flow || layer[edge[nextt[now][i]].to]) continue;
                layer[edge[nextt[now][i]].to] = layer[now] + 1;
                q.push(edge[nextt[now][i]].to);
            }
        }
        return layer[endd];
    }
    int DFS(int now, int value)
    {
        if (now == 403 || !value) return value;
        int ret = 0;
        int floww;
        for (R int i = nextt[now].size() - 1; i >= 0; --i)
        {
            if (!edge[nextt[now][i]].flow || layer[now] != layer[edge[nextt[now][i]].to] - 1) continue;
            int tmp = DFS(edge[nextt[now][i]].to, min(value - ret, edge[nextt[now][i]].flow));
            if (!tmp) continue;
            edge[nextt[now][i]].flow -= tmp;
            edge[nextt[now][i] ^ 1].flow += tmp;
            ret += tmp;
            if(value == tmp) return ret;
        }
        return ret;
    }
    IN bool Dinic()
    {
        int ans = 0; 
        W(BFS(402, 403))
        {
            ans += DFS(402,INF);
         //   printf("%d\n", ans);
        }
        if (ans == tar_num) return true;
        return false;
    }
}
using namespace std;
using namespace Geometry;
using namespace Max_flow;
int main()
{
    R int num;
    R int i , j, k;
    in(man_num), in(tar_num), in(tree_num);
    for (i = 1; i <= man_num; ++i)
    in(man[i].x), in(man[i].y), in(man[i].r), in(halt[i]);
    for (i = 1; i <= tar_num; ++i)
    in(tar[i].x), in(tar[i].y);
    for (i = 1; i <= tree_num; ++i)
    in(tree[i].x), in(tree[i].y), in(tree[i].r);
    for (i = 1; i <=man_num; ++i)
    {  
        num = 0;
        for (j = 1; j <= tar_num; ++j)
        {
            if(dist(tar[j] - man[i]) - man[i].r > -EPS) continue;
            for (k = 1; k <= tree_num; ++k)
            {
                if(is_in(man[i], tree[k])) goto e1;
                if(is_in(tar[j], tree[k])) 
                {
                    printf("-1");
                    return 0;
                }
                if(fabs((tar[j] - man[i]) * (tree[k] - tar[j]) / dist(tar[j] - man[i])) - tree[k].r < -EPS)
                    goto e2;    
            }
            addedge(i, j + 200), vis[j] = true, ++num;
            e2:
            ;
        }
        mx_halt = max(mx_halt, halt[i] * (num  - 1));
        e1:
        ;
    }
    for (i = 1; i <= tar_num; ++i)
    {//判断是否有小精灵不被打到
        if(!vis[i])
        {
            printf("-1");
            return 0;
        }
    }
    for (i = tar_num + 200; i >= 201; --i)
    {
        addedge(i, 403);
    }
    for (i = 1; i <= man_num; ++i)
    {
        addedge(402, i);
    }
    int lef = 0, rig = mx_halt * tar_num, mid;
    int anss;
    W (lef <= rig)//二分时间搜索
    {
    //  printf("%d %d\n", lef, rig);
        mid = (lef + rig) >> 1;
        for (i = 0; i <= cnt; i += 2)
        {//偶数边为正向边, 所以可以如此重置
            edge[i].flow = 1;
            edge[i ^ 1].flow = 0;
        }
        for (i = nextt[402].size() - 1; i >= 0; --i)
        {
            edge[nextt[402][i]].flow = mid/halt[edge[nextt[402][i]].to] + 1;
        }
        if(Dinic())
        {
            anss = mid;
            if(lef == rig) 
            break;
            rig = mid - 1;
        }
        else lef = mid + 1;
    }
    printf("%d", anss);
    return 0;
}
坑点分析
  1. 卡时间加卡精度, 注意边界判断的小于和小于等于的区分以及常数优化。
  2. * 网络流加边需要从0边加起!* 因为0 ^ 1 = 1, 所以偶数边是正向边, 从1开始加的话所有边都错位了…(不过洛谷数据是真的水, 加错位了还可以得80分…)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值