关于jzyzoj——P1341:被污染的牛奶的题解探讨

  题面:

描述 Description    
     你第一天接手三鹿牛奶公司就发生了一件倒霉的事情:公司不小心发送了一批有三聚氰胺的牛奶。很不幸,你发现这件事的时候,有三聚氰胺的牛奶已经进入了送货网。这个送货网很大,而且关系复杂。你知道这批牛奶要发给哪个零售商,但是要把这批牛奶送到他手中有许多种途径。送货网由一些仓库和运输卡车组成,每辆卡车都在各自固定的两个仓库之间单向运输牛奶。在追查这些有三聚氰胺的牛奶的时候,有必要保证它不被送到零售商手里,所以必须使某些运输卡车停止运输,但是停止每辆卡车都会有一定的经济损失。你的任务是,在保证坏牛奶不送到零售商的前提下,制定出停止卡车运输的方案,使损失最小。
输入格式 Input Format    
     第一行: 两个整数N(2<=N<=32)、M(0<=M<=1000), N表示仓库的数目,M表示运输卡车的数量。仓库1代 表发货工厂,仓库N代表有三聚氰胺的牛奶要发往的零售商。 第2..M+1行: 每行3个整数Si,Ei,Ci。其中Si,Ei表示这 辆卡车的出发仓库,目的仓库。Ci(0 <= C i <= 2,000,000) 表示让这辆卡车停止运输的损失。
输出格式 Output Format    
     第1行两个整数c、t,c表示最小的损失,T表示要停止的最少卡车数。接下来t 行表示你要停止哪几条线路。如果有多种方案使损失最小,输出停止的线路最少的方案。如果仍然还有相同的方案,请选择开始输入顺序最小的。
样例输入 Sample Input    
     
4 5
1 3 100
3 2 50
2 4 60
1 2 40
2 3 80 

样例输出 Sample Output    
     
60 1
3

时间限制 Time Limitation    
     1s

  首先这道题的建图模型是不用考虑的,直接按照数据建就行。关键是如何在求出最小割之后求出对应的割集中包含的边的个数以及各个边的编号呢?

  这时候会有一种类似正解的做法:求得最大流之后遍历残余网络中的所有边,对权值为0的边进行输出。

  OJ上的数据较水,用这种方法就直接AC了,但是考虑一下下面的这个图:

  (红色手写的数字为边权值,S为源点,T为汇点,边按照所标记的字典序进行读入)

  那么显然我们会输出j与k,但显然正解是n

  所以这种方法只能水过较水的数据,如果用心构造数据的话可以把这样的算法卡掉。

  所以我们要考虑一个更加正确的算法:

    题目中给出了边数的最大值:1000,那么最小割所包含的边数最多为1000个,因此我们可以把每条边的权值乘上1001在+1,那么求得的maxflow/1001就是最大流,%1001就是边的个数。剩下的还有求出最小割所包含的边,怎么办?我们可以枚举所有的边,然后每次枚举的时候恢复网络,并将枚举到的边权值设为0,如果最大流的变化值等于边权值,那么这个边显然在最小割中。

    但事实上如果用邻接表的话只这样做还会超时,因为第6组数据是1000个(1,4,2000000)的边,每次枚举边权好像还会超时。。。但是我们还可以根据最小割的性质来进行剪枝:当我们找到一个包含在最小割里面的边时,可以不恢复这个边的权值,同时更新一下所求得的最大流。

    这样就能AC了。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<cctype>
 6 #include<ctime>
 7 using namespace std;
 8 int rev[1000010],head[10010],len=0,n,m,forward,queue[10010],h=0,tail=1,level[10010];
 9 long long sum=0,ans;
10 bool vis[10010],check=true;
11 #define oo 0x7fffffff
12 struct node{
13     int y,ne;
14     long long v;
15 }edge[1000010];
16 
17 void addedge(int x,int y,long long v){
18     edge[++len].y=y;    edge[len].v=v;    edge[len].ne=head[x];    head[x]=len;    rev[len]=len+1;
19     edge[++len].y=x;    edge[len].v=0;    edge[len].ne=head[y];    head[y]=len;    rev[len]=len-1;
20 }
21 
22 int read(){
23     int x=0;char ch=getchar();
24     while (!isdigit(ch))    ch=getchar();
25     while (isdigit(ch)){x=x*10+ch-'0';    ch=getchar();}
26     return x;
27 }
28 
29 bool make_level(){
30     h=0;    tail=1;
31     memset(level,-1,sizeof(level));
32     queue[1]=0;    level[0]=0;
33     while (h++<tail){
34         int tn=queue[h];
35         for (int i=head[tn];i;i=edge[i].ne)
36             if (edge[i].v&&level[edge[i].y]<0){
37                 queue[++tail]=edge[i].y;
38                 level[edge[i].y]=level[tn]+1;
39             }
40     }
41     return level[n]>=0;
42 }
43 
44 long long max_flow(int s,long long flow){
45     if (s==n)    return flow;
46     long long maxflow=0,d=0;
47     for (int i=head[s];i&&maxflow<flow;i=edge[i].ne)
48         if (level[edge[i].y]==level[s]+1&&edge[i].v)
49             if (d=max_flow(edge[i].y,min(flow-maxflow,edge[i].v))){
50                 maxflow+=d;
51                 edge[i].v-=d;
52                 edge[rev[i]].v+=d;
53             }
54     if (!maxflow)    level[s]=-1;
55     return maxflow;
56 }
57 
58 void dinic(){
59     long long d;
60     while (make_level())
61         while (d=max_flow(1,oo))sum+=d;
62 }
63 
64 void init(){
65     n=read();    m=read();
66     int x,y,v;
67     for (int i=1;i<=m;i++){
68         x=read();    y=read();    v=read();
69         addedge(x,y,v*1001+1);
70     }
71     addedge(0,1,200000000001001LL);
72 }
73 
74 int main(){
75     init();
76     dinic();
77     long long cnt=sum/1001LL;
78     cnt=cnt*1001LL;
79     cnt=sum-cnt;
80     ans=sum;
81     printf("%lld %lld
",ans/1001,cnt);
82     for (int i=1;i<=m&&cnt;i++){
83         sum=0;    check=true;
84         for (int j=1;j<=len;j+=2){edge[j].v+=edge[rev[j]].v;    edge[rev[j]].v=0;}
85         long long tmp=edge[i*2-1].v;
86         edge[i*2-1].v=0;
87         dinic();
88         if (ans-sum==tmp){
89             printf("%d
",i);
90             cnt--;
91             ans=sum;
92         }
93         else    edge[i*2-1].v=tmp;
94     }
95     return 0;
96 }
AC代码1

  用以上的方法来做,第6组也需要600MS的时间跑过。。。

  我们可以考虑一下另外的对边的处理方法。

  如果不将边权值*1001+1的话,就不需要过多的关于longlong的操作了,那么我们可以省去很大的时间。

  但是如何求解呢?

  最大流直接套用模板就能求出,但是如何求边的个数以及边的编号呢?

  用贪心的方法得证,按照边权值从大到小进行遍历的话,所求得的边数是最小的,那么我们可以对于每个边再用一个结构体e来存储,e.id表示边的编号,e.v表示边权值,那么我们排序一遍之后,依次进行遍历,就能得到最小割的边的个数以及边的编号。

  这样的话我们就极大地节省了longlong运算的时间。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<cctype>
 6 #define oo 0x7fffffff
 7 using namespace std;
 8 int head,tail,level[110],q[110],s,t,sum=0,rev[10010],len=0,lin[110];
 9 int m,n,Id[10010],cnt=0,ans;
10 struct node{
11     int y,v,ne;
12 }edge[10010];
13 
14 struct node1{
15     int v,id;
16 }e[10010];
17 
18 inline bool mycmp(node1 a,node1 b)
19 {return a.v>b.v||a.v==b.v&&a.id<b.id;}
20 
21 inline bool mycmp1(int a,int b)
22 {return a<b;}
23 void addedge(int x,int y,int v){
24     edge[++len].y=y;edge[len].v=v;edge[len].ne=lin[x];lin[x]=len;rev[len]=len+1;
25     edge[++len].y=x;edge[len].v=0;edge[len].ne=lin[y];lin[y]=len;rev[len]=len-1;
26 }
27 
28 int read(){
29     int x=0,y=1;char ch=getchar();
30     while (!isdigit(ch)){if (ch=='-')y=-1;ch=getchar();}
31     while (isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
32     return x*y;
33 }
34 
35 bool make_level(){
36     head=0,tail=1;q[1]=s;memset(level,-1,sizeof(level));level[s]=0;
37     while (head++<tail){
38         int tn=q[head];
39         for (int i=lin[tn],y;i;i=edge[i].ne)
40             if (edge[i].v&&level[y=edge[i].y]==-1){
41                 level[y]=level[tn]+1;
42                 q[++tail]=y;
43             }
44     }
45     return level[t]!=-1;
46 }
47 
48 int max_flow(int k,int flow){
49     if (k==t)    return flow;
50     int maxflow=0,d;
51     for (int i=lin[k];i&&maxflow<flow;i=edge[i].ne)
52         if (edge[i].v&&level[edge[i].y]==level[k]+1)
53             if (d=max_flow(edge[i].y,min(flow-maxflow,edge[i].v))){
54                 edge[i].v-=d;edge[rev[i]].v+=d;maxflow+=d;
55             }
56     if (!maxflow)level[k]=-1;
57     return maxflow;
58 }
59 
60 void dinic(){
61     int d;
62     while (make_level())    while (d=max_flow(s,oo))    sum+=d;
63 }
64 
65 void init(){
66     n=read();m=read();
67     s=1,t=n;
68     int x,y,v;
69     for (int i=1;i<=m;i++){
70         x=read();y=read();v=read();
71         addedge(x,y,v);
72     }
73 }
74 
75 int main(){
76     init();
77     dinic();
78     printf("%d ",sum);
79     ans=sum;
80     for (int j=1;j<=len;j+=2){edge[j].v+=edge[rev[j]].v;edge[rev[j]].v=0;}
81     for (int i=1;i<=m;i++){e[i].v=edge[i*2-1].v;e[i].id=i;}
82     sort(e+1,e+1+m,mycmp);
83     for (int i=1;i<=m&&ans;i++){
84         int id=e[i].id,v=e[i].v;
85         sum=0;
86         edge[id*2-1].v=0;
87         dinic();
88         for (int j=1;j<=len;j+=2){edge[j].v+=edge[rev[j]].v;edge[rev[j]].v=0;}
89         if (ans-sum==v){
90             ans=sum;
91             Id[++cnt]=id;
92         }
93         else edge[id*2-1].v=v;
94     }
95     printf("%d
",cnt);
96     sort(Id+1,Id+1+cnt);
97     for (int i=1;i<=cnt;i++)    printf("%d
",Id[i]);
98     return 0;
99 }
AC代码2

  用这种方法每组数据都能在100ms之内跑过。

  对于网络流来说,我们不仅仅需要考虑的是建图的方式,还有关于边的各种考虑,大概这就是其难度所在吧= =

原文地址:https://www.cnblogs.com/hinanawitenshi/p/6910609.html