URAL 1076:费用流解法

题意:
数字N表示有N个垃圾箱,而且刚好存在N类垃圾
每行输入的是第i个垃圾箱装有 x 数量 的第j类垃圾;
现在要你将这些垃圾分类,也就是把同类垃圾放在同一个垃圾箱里,
而且每个垃圾箱只能放一类垃圾。。。
每次把一个单元垃圾从一个垃圾箱移动到另一个垃圾箱就要1 effort;
求最小的 effort;

比赛时看数据开始以为dp能行,后来测试数据发现自己的状态转移方程是错的。。。
根本没有最优子结构;
比赛完后看题解,原来是 二分图的最佳匹配,
以前只会 最大匹配,,,然后看了一晚上,实在是难以理解
大家可以去看看
@wanggp3
http://www.cnblogs.com/wanggp3/p/3762568.html

最后实在不行了,突然发现好像最小费用流可以做。。。
只要自己建立 超级源,超级汇就可以了(常用套路)
1–N点 表示垃圾种类,N+1–N+N点 表示垃圾箱
每类垃圾 对 每个垃圾箱之间都存在边,容量为1,而费用就是
该类垃圾的总数-在该垃圾箱里已有的该类垃圾数量(sum[j] - map[i][j])

超级源点0 与 1–N 点(垃圾种类)都有边,容量为1, 费用为0;
超级汇点N+N+1 与 N+1–N+N点(垃圾箱)都有边,容量为1,费用为0;

然后计算,
最开始提交时超时了QAQ。。。以为费用流会超时。。。
后来网上看到有人说是可以用费用流做的,然后回来检查一下。。。
CHX 发现了 是在
for(int i=0; i

#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 = 350;

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()
{
   for(int i=0; i<=MAXN; ++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)
{
      //memset(dis,0,sizeof(dis));
     for(int i=0; i<MAXN; ++i)
         dis[i] =  INF;
     memset(vis,0,sizeof(vis));

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

     queue<int> q;
     q.push(s);
   //  vector<int>::iterator it;
     while(!q.empty())
     {
          int u = q.front();
          q.pop();
          vis[u] = 0;


      //    for(it = v[u].begin();it!=v[u].end();it++)
        //  {  //int i = it - v[u].end();
       //         Node &tmp =  e[(*it)];

        //  }
          int s1 = v[u].size();
          for(int i=0; i<s1; ++i)
          {
              Node &tmp = e[v[u][i]];  
              if(tmp.cap> tmp.flow && dis[tmp.to]>dis[u]+tmp.cost)
              {
                 dis[tmp.to] = dis[u] + tmp.cost;
                 father[tmp.to] = v[u][i];        // (*it);
                 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 all_trash[MAXN];
int map1[MAXN][MAXN];
int main()
{
    int N;
    while(scanf("%d",&N)!=EOF)
    {

        Clear();
        memset(all_trash,0,sizeof(all_trash));

        int x;
        for(int i=1; i<=N ;++i)
          for(int j=1; j<=N; ++j)
          {
             scanf("%d",&map1[i][j]);
             all_trash[j] += map1[i][j];
          }

        int u,v,w;
        ///容量是1
      for(int i=1; i<=N ;++i)
        for(int j=1; j<=N; ++j)
        {
            //scanf("%d%d%d",&u,&v,&w);
            u = i;
            v = j+N;
            w = all_trash[j] - map1[i][j];
            add_Node(u,v,1,w);
        //    add_Node(v,u,1,w);  ///双向边

        }

        for(int i=1; i<=N; ++i)
             add_Node(0,i,1,0);

        for(int j=N+1; j<=N+N; ++j)
             add_Node(j,N+N+1,1,0);

        printf("%d\n",Min_Cost(0,N+N+1));

    }
    return 0;
}


``

“`

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值