[USACO4.4] 追查坏牛奶 Pollutant Control

题目描述

你第一天接手三鹿牛奶公司就发生了一件倒霉的事情:公司不小心发送了一批有三聚氰胺的牛奶。

很不幸,你发现这件事的时候,有三聚氰胺的牛奶已经进入了送货网。这个送货网很大,而且关系复杂。你知道这批牛奶要发给哪个零售商,但是要把这批牛奶送到他手中有许多种途径。

送货网由一些仓库和运输卡车组成,每辆卡车都在各自固定的两个仓库之间单向运输牛奶。在追查这些有三聚氰胺的牛奶的时候,有必要保证它不被送到零售商手里,所以必须使某些运输卡车停止运输,但是停止每辆卡车都会有一定的经济损失。

你的任务是,在保证坏牛奶不送到零售商的前提下,制定出停止卡车运输的方案,使损失最小。

输入格式

第 11 行两个整数 NN、MM,NN 表示仓库的数目,MM 表示运输卡车的数量。仓库 11 代表发货工厂,仓库 NN 代表有三聚氰胺的牛奶要发往的零售商。

第 2\sim M+12∼M+1 行,每行 33 个整数 S_iSi​、E_iEi​ 和 C_iCi​。其中 S_iSi​、E_iEi​ 分别表示这辆卡车的出发仓库和目的仓库。C_iCi​ 表示让这辆卡车停止运输的损失。

输出格式

两个整数 CC 和 TT,CC 表示最小的损失,TT 表示在损失最小的前提下,最少要停止的卡车数。

输入输出样例

输入 #1复制

4 5
1 3 100
3 2 50
2 4 60
1 2 40
2 3 80

输出 #1复制

60 1

说明/提示

对于 100 \%100% 的数据,满足 2 \le N \le 322≤N≤32,0 \le M \le 10^30≤M≤103,1 \le S_i \le N1≤Si​≤N,1 \le E_i \le N1≤Ei​≤N,0 \le C_i \le 2 \times 10^60≤Ci​≤2×106。

题目翻译来自 NOCOW。

USACO Training Section 4.4

这一道题是由最小割转最大流

我就这样解释吧:

最大流是从点1能流到n的最大流量,流量的大小主要是由每

条路的最小边决定的(大概是这样的)

最小割为了消耗费用最小,就肯定要割去最小消耗的边。也

可以这样说,先找出1到n的最大流,把这些流量全部切掉,

就是最小割(很多条边都是多余的)

感觉和网络流一模一样

关于第二问:

因为要找最少的边(有的oj还要找出边的标号),所以要把

边从大到小排一次序,就能更好的找出“最少的边”,为了出

现低级错误,我就用了一个用时多一些但是不容易出错的方

法来做

如果要找出这些边,就可以这样想一想,有一些边是“重要

的”,就是说这一条边是满流量的,而且这种边是直接关系到

这条路(不是边)到终点的流量(就是刚刚说的最小边,流

量是由最小边决定的)

其实就是

把这一条边去掉后的最大流+这条边的流量=最大流,

只要是找出这样的边就可以了

注意:这里有一个细节,就是可能会出现多条这样的边,这

些边之和大于最大流,还有就是同一条路可能存在两条同流

量的边,这样的边就只要找一条

代码如下:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
struct node
{
    int x,y,c,next,other,bk;
}a[2100],d[2100];int len,last[51];
void ins(int x,int y,int c)//网络流就不说了 
{
    len++;int k1=len;
    a[len].x=x;a[len].y=y;a[len].c=c;
    a[len].next=last[x];last[x]=len;

    len++;int k2=len;
    a[len].x=y;a[len].y=x;a[len].c=0;
    a[len].next=last[y];last[y]=len;

    a[k1].other=k2;
    a[k2].other=k1;
}
int list[2100],head,tail,h[51];
int st,ed,b[2100],lenb;
bool bt_h()
{
    memset(h,0,sizeof(h));h[st]=1;
    list[1]=st;head=1;tail=2;
    while(head!=tail)
    {
        int x=list[head];
        for(int k=last[x];k;k=a[k].next)
        {
            int y=a[k].y;
            if(h[y]==0 && a[k].c>0)
            {
                h[y]=h[x]+1;
                list[tail]=y;
                tail++;
            }
        }
        head++;
    }
    if(h[ed]==0)return false;
    else return true;
}
int findflow(int x,int f)
{
    if(x==ed)return f;
    int s=0;
    for(int k=last[x];k;k=a[k].next)
    {
        int y=a[k].y;
        if(h[y]==h[x]+1&&a[k].c>0&&s<f)
        {
            int t=findflow(y,min(a[k].c,f-s));
            s+=t;a[k].c-=t;a[a[k].other].c+=t;
        }
    }
    if(s==0)h[x]=0;
    return s;
}
int cmp(const void *xx,const void *yy)//排一次序
{
    node n1=*(node *)xx;
    node n2=*(node *)yy;
    if(n1.c<n2.c) return 1;//按流量从大到小排一次序 
    if(n1.c>n2.c) return -1;
    //这里有一个疑惑:为什么同流量不要排编号
	//因为总是小的编号在前面 
    return 0;
}
bool bk[21000];
int main()
{
    int n,m;scanf("%d%d",&n,&m);
    st=1;ed=n;
    len=0;memset(last,0,sizeof(last));
    for(int i=1;i<=m;i++)
    {
        int x,y,c;scanf("%d%d%d",&x,&y,&c);ins(x,y,c);//建边 
        d[i].x=x;d[i].y=y;d[i].c=c;d[i].bk=i;//记录 
    }
    int ans=0;
    while(bt_h()==true) ans=ans+findflow(st,2147483647);//找一次最大流 
    printf("%d",ans);
    qsort(d+1,m,sizeof(node),cmp);
    memset(bk,true,sizeof(bk));//一开始所有的边都是可以用的 
    for(int i=1;i<=m;i++)
    {
    	if(ans==0) break;//如果流量为0,就退出 
    	if(d[i].c<=ans)
    	{
    		len=0;memset(last,0,sizeof(last));//初始化 
    		for(int j=1;j<=m;j++)//建边 
    		{
    			if(i==j || bk[d[j].bk]==false) continue;
    			ins(d[j].x,d[j].y,d[j].c);
    		}
    		int s=0;
    		while(bt_h()==true) s=s+findflow(st,2147483647);//再找一次网络流 
    		if(s+d[i].c==ans)
    		{
    			ans-=d[i].c;
    			bk[d[i].bk]=false;//把这一条边设置为不能用 
    			lenb++;b[lenb]=d[i].bk;//记录 
    		}
    	}
    }
    printf(" %d\n",lenb);//输出 
    if(lenb!=0)
    {
        sort(b+1,b+lenb+1);//排序 
        for(int i=1;i<=lenb;i++)printf("%d\n",b[i]);//输出 
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值