zoj 3460 Missile 【二分 + 二分图匹配】 【经典建模】 【二分 + 最大流】

Missile

Time Limit: 2 Seconds      Memory Limit: 65536 KB

You control N missile launching towers. Every tower has enough missiles, but for each tower only one missile can be launch at the same time. Before the launching, every missile needT1 seconds to leave the tower. Assume that all the missiles have the same speedV, and it would fly along the shortest path to the target. You can just consider the horizontal distance and ignore the height. You can consider the time equal to distance / V (minutes). The missile can immediately destroy the target when it reached. Besides, for the same tower, after launching a missile, it need T2 minutes to prepare for the next one.

Now, give you the coordinate position of N missile launching towers andM targets, T1, T2 and V, you should find the minimum minutes to destroy all the targets.

Input

The input will consist of about 10 cases. The first line of each case contains five positive integer numbersN, M, T1, T2 and V, decribed as above. The nextM lines contains two integer numbers indicating the coordinate of M targets. The continueingN lines contains two integer numbers indicating the coordinate of N towers.
To all the cases, 1 ≤ N ≤ 50, 1 ≤ M ≤ 50
The absolute value of all the coordinates will not exceed 10000, T1,T2, V will not exceed 2000.

Output

For each case, the output is only one line containing only one real number with six digits precision (after a decimal point) indicating the minimum minutes to destroy all the targets.

Sample Input
3 3 30 20 1
0 0
0 50
50 0
50 50
0 1000
1000 0
Sample Output

91.500000


题意:用N个导弹发射塔攻击M个目标。每个导弹发射塔只能同时为一颗导弹服务,发射一颗导弹后需要T1(这里用的是秒)的时间才能离开当前的导弹发射塔,一颗导弹从发射到击中目标的时间与目标到发射塔的距离有关(直线距离),每颗导弹发射完成之后发射塔需要T2的时间准备下一个。现在给出N个导弹发射塔和M个目标的位置坐标以及T1,T2,V,问用这N个导弹发射塔最少需要多少时间可以击毁所有M个目标。


具体实现

一:对每一个导弹发射器,它击中一个目标共有M种情况:分别为在该发射塔第一次发射、第二次发射、第三次发射...一直到第M次发射。因此我们可以把每一个导弹发射塔拆分成M个发射塔,它们与同一个目标的距离是一样的,唯一不同是T1、T2的花销占时不一样。


二:这样的话我们就得到了N*M个点发射器到 M个目标的映射,所表示的关系是当前发射塔击中目标的耗时。


三:我们可以用二分来查找最优时间。对于每一次查找的时间值mid,在这N*M -> M的所有关系中,若耗时小于或等于mid,那么就建边。建完后,求最大匹配,若最大匹配为M(即可以摧毁M个目标)那么压缩时间继续查找,反之增大时间继续查找。


AC代码:匹配代码 80ms


#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
#include <vector>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#define MAXN 3000
#define MAXM 200000
#define INF 1000000000
#define eps 1e-8
#define DD double
using namespace std;
struct rec
{
    DD x, y;
};
rec PP[60], TT[60];
vector<int> G[60];
bool used[MAXN];
int match[MAXN];
DD dist[60][60];//存储i导弹发射塔到j目标的距离
DD time[MAXN][60];//存储当前导弹发射塔打中目标所需时间
int N, M;
DD T1, T2, V;
DD Dis(DD x1, DD y1, DD x2, DD y2)
{
    return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
void input()
{
    T1 /= 60;//注意这里
    for(int i = 1; i <= M; i++)
        scanf("%lf%lf", &TT[i].x, &TT[i].y);
    for(int i = 1; i <= N; i++)//N个导弹发射塔
    {
        scanf("%lf%lf", &PP[i].x, &PP[i].y);
        for(int j = 1; j <= M; j++)
            dist[i][j] = Dis(PP[i].x, PP[i].y, TT[j].x, TT[j].y);
    }
    for(int i = 1; i <= N; i++)//遍历所有导弹发射塔
    {
        for(int k = 1; k <= M; k++)//拆分成M个 分别表示刚打过0、1、2...M-1个目标后再去打当前目标
        {
            for(int j = 1; j <= M; j++)//要打的目标
                time[(i-1)*M + k][j] = T1 * k + T2 * (k - 1) + dist[i][j] / V;
        }
    }
}
void init()
{
    for(int i = 1; i <= M; i++)
        G[i].clear();
}
void getMap(DD mid)
{
    for(int i = 1; i <= N*M; i++)//遍历
    {
        for(int j = 1; j <= M; j++)
        {
            if(mid >= time[i][j])
                G[j].push_back(i);
        }
    }
}
int find(int x)
{
    for(int i = 0; i < G[x].size(); i++)
    {
        int y = G[x][i];
        if(!used[y])
        {
            used[y] = 1;
            if(match[y] == -1 || find(match[y]))
            {
                match[y] = x;
                return 1;
            }
        }
    }
    return 0;
}
int Match()
{
    memset(match, -1, sizeof(match));
    int ans = 0;
    for(int i = 1; i <= M; i++)
    {
        memset(used, false, sizeof(used));
        ans += find(i);
    }
    return ans;
}
void solve()//二分查找
{
    DD left = 0, right = 200000000000.0, mid;
    while(right - left >= eps)
    {
        mid = (right + left) / 2;
        init();
        getMap(mid);//建图
        if(Match() == M)//匹配值
            right = mid;
        else
            left = mid;
    }
    printf("%.6lf\n", right);
}
int main()
{
    while(scanf("%d%d%lf%lf%lf", &N, &M, &T1, &T2, &V) != EOF)
    {
        input();
        solve();
    }
    return 0;
}





最大流建图


一:设立超级源点0,超级汇点(N + 1) * M + 1。


二:超级源点到每个目标引一条容量为1的边,每个发射塔(拆分后的)到超级汇点引一条容量为1的边,按若小于当前查询时间的关系(同匹配建边)建边容量为1。

然后跑最大流,判断最大流是否为M。 接着二分。。。


AC代码 :400ms 点较多。。。

#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
#include <vector>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#define MAXN 4000
#define MAXM 300000
#define INF 1000000000
#define eps 1e-8
#define DD double
using namespace std;
struct Edge
{
    int from, to, cap, flow, next;
};
Edge edge[MAXM];
int head[MAXN], edgenum, cur[MAXN];
int DIST[MAXN];
bool vis[MAXN];
struct rec
{
    DD x, y;
};
rec PP[60], TT[60];
DD dist[60][60];//存储i导弹到j目标的距离
DD time[MAXN][60];//存储当前导弹发射地打中目标所需时间
int N, M;
DD T1, T2, V;
DD Dis(DD x1, DD y1, DD x2, DD y2)
{
    return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
void input()
{
    T1 /= 60;//注意这里
    for(int i = 1; i <= M; i++)
        scanf("%lf%lf", &TT[i].x, &TT[i].y);
    for(int i = 1; i <= N; i++)//N个导弹发射地
    {
        scanf("%lf%lf", &PP[i].x, &PP[i].y);
        for(int j = 1; j <= M; j++)
            dist[i][j] = Dis(PP[i].x, PP[i].y, TT[j].x, TT[j].y);
    }
    for(int i = 1; i <= N; i++)//遍历所有导弹发射地
    {
        for(int k = 1; k <= M; k++)//拆分成M个 分别表示刚打过0、1、2...M-1个目标后再去打当前目标
        {
            for(int j = 1; j <= M; j++)//要打的目标
                time[(i-1)*M + k][j] = T1 * k + T2 * (k - 1) + dist[i][j] / V;
        }
    }
}
void init()
{
    edgenum = 0;
    memset(head, -1, sizeof(head));
}
void addEdge(int u, int v, int w)
{
    Edge E1 = {u, v, w, 0, head[u]};
    edge[edgenum] = E1;
    head[u] = edgenum++;
    Edge E2 = {v, u, 0, 0, head[v]};
    edge[edgenum] = E2;
    head[v] = edgenum++;
}
void getMap(DD mid)
{
    for(int i = 1; i <= M; i++)
        addEdge(0, i, 1);//超级源点到目标引一条容量为1的边
    for(int i = 1; i <= N*M; i++)
        addEdge(i + M, (N + 1) * M + 1, 1);
    for(int i = 1; i <= N*M; i++)//遍历
    {
        for(int j = 1; j <= M; j++)
        {
            if(mid >= time[i][j])
                addEdge(j, i + M, 1);
        }
    }
}
bool BFS(int start, int end)
{
    queue<int> Q;
    memset(DIST, -1, sizeof(DIST));
    memset(vis, false, sizeof(vis));
    DIST[start] = 0;
    vis[start] = true;
    Q.push(start);
    while(!Q.empty())
    {
        int u = Q.front();
        Q.pop();
        for(int i = head[u]; i != -1; i = edge[i].next)
        {
            Edge E = edge[i];
            if(!vis[E.to] && E.cap > E.flow)
            {
                vis[E.to] = true;
                DIST[E.to] = DIST[u] + 1;
                if(E.to == end) return true;
                Q.push(E.to);
            }
        }
    }
    return false;
}
int DFS(int x, int a, int end)
{
    if(x == end || a == 0) return a;
    int flow = 0, f;
    for(int &i = cur[x]; i != -1; i = edge[i].next)
    {
        Edge &E = edge[i];
        if(DIST[E.to] == DIST[x] + 1 && (f = DFS(E.to, min(E.cap - E.flow, a), end)) > 0)
        {
            E.flow += f;
            edge[i^1].flow -= f;
            flow += f;
            a -= f;
            if(a == 0) break;
        }
    }
    return flow;
}
int Maxflow(int start, int end)
{
    int flow = 0;
    while(BFS(start, end))
    {
        memcpy(cur, head, sizeof(head));
        flow += DFS(start, INF, end);
    }
    return flow;
}
void solve()
{
    DD left = 0, right = 200000000000.0, mid;
    while(right - left >= eps)
    {
        mid = (right + left) / 2;
        init();
        getMap(mid);
        if(Maxflow(0, (N + 1) * M + 1) == M)
            right = mid;
        else
            left = mid;
    }
    printf("%.6lf\n", right);
}
int main()
{
    while(scanf("%d%d%lf%lf%lf", &N, &M, &T1, &T2, &V) != EOF)
    {
        input();
        solve();
    }
    return 0;
}







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值