题目描述
你第一天接手三鹿牛奶公司就发生了一件倒霉的事情:公司不小心发送了一批有三聚氰胺的牛奶。
很不幸,你发现这件事的时候,有三聚氰胺的牛奶已经进入了送货网。这个送货网很大,而且关系复杂。你知道这批牛奶要发给哪个零售商,但是要把这批牛奶送到他手中有许多种途径。
送货网由一些仓库和运输卡车组成,每辆卡车都在各自固定的两个仓库之间单向运输牛奶。在追查这些有三聚氰胺的牛奶的时候,有必要保证它不被送到零售商手里,所以必须使某些运输卡车停止运输,但是停止每辆卡车都会有一定的经济损失。
你的任务是,在保证坏牛奶不送到零售商的前提下,制定出停止卡车运输的方案,使损失最小。
输入格式
第 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;
}