费用流+建图——Teamwork ( HDU 4494 )

  • 题目链接:
    http://acm.split.hdu.edu.cn/showproblem.php?pid=4494

  • 分析:
    给出n个仓库和m种工人,每个仓库有一个坐标和开始建造的时间与建造所需要的时间,并且建造每个工厂对m种工人的需求量是不一样的。时间从0开始,要求每到达一个工厂的开始建造时间,工厂必须开始建造,工人从一个工厂到另外一个工厂的时间花费等于两个工厂间的直线距离,最开始从原点(0, 0)派出工人到达工厂(这段路程不计时),求一开始最少要派出多少名工人才能保证所有工厂如期交工。

  • 题解:
    这道题可以转换成费用流来解决,每种工人是互不相干的,所以我们可以针对每一种工人算出其最小需要的数量,因为工人可以在工厂间移动,所以我们需要把工厂拆点来建图:

    对每一种工人都单独建图;

    先从超级源点(0, 0)出发向每一个工厂(2~n)建边,费用为1,流量为这个工厂对这种工人的需求总量;(表示从原点派出的工人)

    再从超级源点(0, 0)出发向每一个工厂(n+2~2n)建边,费用为0,流量为这个工厂对这种工人的需求总量。(表示要从这个工厂转移出去的工人)

    接着从每一个初始工厂(2~n)往最终工厂(2n+2~3n)分别建边,表示刚开始派出多少人到这个工厂,再计算时间,如果时间足够转移则从工厂(n+2~2n)建边到对应的最终工厂(2n+2~3n)

    主要的做法就是把工厂拆成了三个点,第一层的点表示初始从源点派到该工厂的工人,第一层的点直接连接到第三层的点,即这个工厂最终满足人数的状态,第二层点表示满足转移出去的时间条件的工厂,第二层点建边到对应的被转移进去第三层工厂。这样就把工人移动这个操作给融入到图里去了。最后求出最大流最小费用即可。

  • 注意: 在费用流SPFA里,对初始dis数组赋值时范围一定要囊括所有点。

  • 建图代码:

int solve()
{
    S = 1;          //起点
    E = 3*n+1;      //终点
    int Ans =0;
    for(int i=1;i<=m;i++)
    {
        build(i);        //对每一种工人建图
        Ans += MCF(S, E);//求出每一个图的费用流,即每一种工人最少需求量。 
    }
    return Ans;
}

void build(int type)
{
    init();
    for(int i=2;i<=n;i++)
    {
        Add(1,i,node[i].worker[type],1);
        Add(i,2*n+i,node[i].worker[type],0);
        Add(1,n+i,node[i].worker[type],0);
        Add(2*n+i,3*n+1,node[i].worker[type],0);
        for(int j=2;j<=n;j++)
        {
                if(node[i].begin+node[i].cost+dist[i][j] <= node[j].begin)//判断i工厂的工人能否转移到j工厂去
            {
                Add(n+i, 2*n+j, node[i].worker[type],0);
            }
        }
    }
}
  • AC代码:
    这里写图片描述
/*************************************************************************
    > File Name: TeamworkHDU4494.cpp
    > Author: Akira 
    > Mail: qaq.febr2.qaq@gmail.com 
    > Created Time: 2016年08月26日 星期五 19时41分29秒
 ************************************************************************/

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <algorithm>
#include <queue>
#include <stack>
#include <map>
#include <cmath>
#include <vector>
#include <set>
#include <list>
#include <ctime>
typedef long long LL;
typedef unsigned long long ULL;
typedef long double LD;
#define MST(a,b) memset(a,b,sizeof(a))
#define CLR(a) MST(a,0)
#define Sqr(a) ((a)*(a))
using namespace std;

#define MaxN 233*3
#define MaxM MaxN*MaxN*10
#define INF 1000000000
#define bug cout<<88888888<<endl;

int T;
int n,m;

struct Node
{
    double x,y;
    int begin;
    int cost;
    int worker[6];
}node[MaxN];

struct Edge
{
    int u,v;
    int next;
    int flow;
    int cost;
}edge[MaxM];
int S,E;
int head[MaxN];
int cont;

void add(int u, int v, int flow, int cost)
{
    edge[cont].v = v;
    edge[cont].u = u;
    edge[cont].flow = flow;
    edge[cont].cost = cost;
    edge[cont].next = head[u];
    head[u] = cont++;
}

void Add(int u, int v, int flow, int cost)
{
    add(u, v, flow, cost);  //正向边
    add(v, u, 0, -cost);      //反向退流边
}

double dist[MaxN][MaxN];

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

void init()
{
    cont = 0;
    MST(head,-1);
}

void build(int type)
{
    init();
    for(int i=2;i<=n;i++)
    {
        Add(1,i,node[i].worker[type],1);
        Add(i,2*n+i,node[i].worker[type],0);
        Add(1,n+i,node[i].worker[type],0);
        Add(2*n+i,3*n+1,node[i].worker[type],0);
        for(int j=2;j<=n;j++)
        {
            if(node[i].begin+node[i].cost+dist[i][j] <= node[j].begin)
            {
                Add(n+i, 2*n+j, node[i].worker[type],0);
            }
        }
    }
}

bool vis [MaxN];
int dis [MaxN];
int pre [MaxN];
int aug;
int ans;
bool SPFA(int s, int t)
{
    int k, p, V;
    queue<int> q;
    MST(pre, -1);
    CLR(vis);
    for(int i=0;i<MaxN;i++)
        dis[i] = INF;
    q.push(s);
    vis[s] = 1;
    dis[s] = 0;
    while(!q.empty())
    {
        k = q.front();
        q.pop();
        vis[k] = 0;
        for(p = head[k]; p!=-1; p = edge[p].next)
        {
            V = edge[p].v;
            if(edge[p].flow && (edge[p].cost + dis[k] < dis[V]))
            {
                dis[V] = edge[p].cost + dis[k];
                pre[V] = p;
                if(!vis[V])
                {
                    q.push(V);
                    vis[V] = 1;
                }
            }
        }
    }
    if(dis[t] == INF) return false;
    aug = INF+1;
    for(p = pre[t]; p != -1; p = pre[edge[p].u])
    {
        aug = min(aug, edge[p].flow);
    }

    for(p = pre[t]; p != -1; p = pre[edge[p].u])
    {
        edge[p].flow -= aug;
        edge[p^1].flow += aug;
    }
    ans += dis[t]*aug;
    return true;
}

int MCF(int s, int t)
{
    ans = 0;
    while(SPFA(s,t));
    return ans;
}

int solve()
{
    S = 1;
    E = 3*n+1;
    int Ans =0;
    for(int i=1;i<=m;i++)
    {
        build(i);
        Ans += MCF(S, E);
    }
    return Ans;
}

int main()
{
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d%d", &n, &m);
        scanf("%lf%lf", &node[1].x, &node[1].y);

        for(int i=2;i<=n;i++)
        {
            scanf("%lf%lf%d%d", &node[i].x, &node[i].y, &node[i].begin, &node[i].cost);
            for(int j=1;j<=m;j++)
            {
                scanf("%d", &node[i].worker[j]);
            }
        }

        for(int i=1;i<=n;i++)
        {
            for(int j=i+1;j<=n;j++)
            {
                dist[i][j] = dist[j][i] = GetDist(node[i], node[j]);
            }
        }

        printf("%d\n", solve());

    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值