poj2516:神烦的最小费建图

题意:
看了很久,,,好无语的输入。。。还是借鉴下网上的意思吧,比我说得清楚多了,可以转化为一道最小费用流问题

@小優YoU
http://blog.csdn.net/lyy289065406/article/details/6742534

//poj2516
//Memory: 952K
//Time: 360MS
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
#include<cmath>
using namespace std;

const int INF = 1e9+9;
const int MAXN = 105;

struct Node
{
    int from;   //左
    int to;     //右
    int cap;    //容量
    int flow;    //流量
    int cost;    //费用
};

vector <Node> e;  ///一条边的所有属性
vector <int> v[MAXN];   ///记录边的位置
int vis[MAXN];
int dis[MAXN];

int father[MAXN],pos[MAXN];  ///father保存父节点, pos保存cap

void Clear(int x)
{
   for(int i=0; i<=x; ++i)
      v[i].clear();

   e.clear();

}

void add_Node(int from, int to, int cap, int cost)
{
     e.push_back((Node){from,to,cap,0,cost});
     e.push_back((Node){to,from,0,0,-cost});   ///反向边

    int len = e.size()-1;
    v[to].push_back(len);
    v[from].push_back(len-1);

}

bool SPFA(int s, int t, int& flow, int& cost)
{
     for(int i=0; i<MAXN; ++i)
         dis[i] =  INF;
     memset(vis,0,sizeof(vis));

     dis[s] = 0;     //最短路径,即 最小费用
     vis[s] = 1;    //标记是否在队列里
     father[s] = 0;      //p保存father,
     pos[s] = INF;    //pos保存cap

     queue<int> q;
     q.push(s);

     while(!q.empty())
     {//cout<<"1"<<endl;
          int u = q.front();
          q.pop();
          vis[u] = 0;

          for(int i=0; i<v[u].size(); ++i)
          {
              Node &tmp = e[v[u][i]];  /
              if(tmp.cap> tmp.flow && dis[tmp.to]>dis[u]+tmp.cost)
              {//cout<<u<<"   "<<tmp.to<<"    "<<dis[t]<<endl;
                 dis[tmp.to] = dis[u] + tmp.cost;
                 father[tmp.to] = v[u][i];  ///哦? 头节点的设置  ,方便从t到s 的更新
                 pos[tmp.to] = min(pos[u], tmp.cap-tmp.flow);  ///更新边的剩余容量(最大流量),记录最小的

                 if(!vis[tmp.to])
                  {
                     q.push(tmp.to);
                     vis[tmp.to] = 1;
                  }

               }
           }
      }
      if(dis[t]==INF) return false;

      flow += pos[t];      ///总(扩增)流量。。
      cost += dis[t]*pos[t]; ///???费用的计算方法,要好好理解。。。

      int t2 = t;
     while(t2 != s)  //回去???
     {
         e[father[t2]].flow += pos[t];   //更新流量,为了下次寻找新的最小费用
         e[father[t2]^1].flow -= pos[t];   //反向边

         t2 = e[father[t2]].from;
      }
return true;
}

int Min_Cost(int s,int t)
{
    int flow = 0;
    int cost = 0;
    while(SPFA(s,t,flow,cost));

    return cost;
}

int need[MAXN][MAXN];  //i需求者需要need[i][j]个第j个物品
int have[MAXN][MAXN];  //i供应站有have[i][j]个第j个物品
int all_n[MAXN];
int all_h[MAXN];

int main()
{
    ///题目是双向边,那么反向边会不会造成混淆呢???
    ///
    int N,M,K;
    while(scanf("%d%d%d",&N,&M,&K))
    {
        if(!N&&!M&&!K) break;
      //  Clear(MAXN);
        memset(need,0,sizeof(need));
        memset(have,0,sizeof(have));
        memset(all_n,0,sizeof(all_n));
        memset(all_h,0,sizeof(all_h));
        //
        for(int i=1; i<=N; ++i)
          for(int j=1; j<=K; ++j)
             {
                 scanf("%d",&need[i][j]);
                 all_n[j] += need[i][j];
             }
        for(int i=1; i<=M; ++i)
          for(int j=1; j<=K; ++j)
             {
                scanf("%d",&have[i][j]);
                all_h[j] += have[i][j];
             }

        int flag = 0;
        for(int j=1; j<=K; ++j)
          if(all_h[j]<all_n[j]) {flag=1; break;}


        int ans = 0;

        int value;
        for(int k=1; k<=K; ++k)
        {
            Clear(MAXN);

            for(int i=1; i<=N; ++i)
             for(int j=1; j<=M; ++j)
             {
                scanf("%d",&value);

                if(flag) continue;

                add_Node(j,M+i,have[j][k],value);///
             }
             if(flag) continue;

            for(int p=1; p<=M; ++p)
                add_Node(0,p,have[p][k],0);///

            for(int q=1; q<=N; ++q)
                add_Node(M+q,M+N+1,need[q][k],0);///

            int cost =  Min_Cost(0,M+N+1);
             ans += cost;
        }
      if(flag) printf("-1\n");
        else printf("%d\n",ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值