【UVA 11865】 Stream My Contest (二分+MDST最小树形图)

【题意】

  你需要花费不超过cost元来搭建一个比赛网络。网络中有n台机器,编号0~n-1,其中机器0为服务器,其他机器为客户机。一共有m条可以使用的网线,其中第i条网线的发送端是机器ui,接收端是机器vi(数据只能从机器ui单向传输到机器vi),带宽是bi Kbps,费用是ci元。每台客户机应当恰好从一台机器接收数据。你的任务是最大化网络中的最小带宽。

Input
First line of input contains T (≤ 50), the number of test cases. This is followed by T test cases.
Each test case starts with an integer N, M, C (1 ≤ N ≤ 60, 1 ≤ M ≤ 10^4, 1 ≤ C ≤ 10^9), the
number of universities and the number of possible links, and the budget for setting up the network.
Each university is identified by an integer between 0 and N − 1, where 0 is the server.
Next M lines each contain 4 integers, u, v, b, c (0 ≤ u, v < N, 1 ≤ b, c ≤ 10^6, u ̸= v), describing
a possible link from university u to university v, that has the bandwidth of b kbps and of cost c. All
links are unidirectional.
There is a blank line before each test case.
Output
For each test case, output the maximum bandwidth to stream.
If it’s not possible, output ‘streaming not possible.’.
Sample Input
3
3 4 300
0 1 128 100
1 2 256 200
2 1 256 200
0 2 512 300
3 4 500
0 1 128 100
1 2 256 200
2 1 256 200
0 2 512 300
3 4 100
0 1 128 100
1 2 256 200
2 1 256 200
0 2 512 300
Sample Output
128 kbps
256 kbps
streaming not possible.

【分析】

  一开始看错题了ORZ,直接无视有向图,直接二分+MST。。

   好吧看过来之后就是多了个方向而已。。

  那就是MDST,要用朱-刘算法,,中国人发明的吧??

  hhhh

  1 #include<cstdio>
  2 #include<cstdlib>
  3 #include<cstring>
  4 #include<iostream>
  5 #include<algorithm>
  6 #include<queue>
  7 using namespace std;
  8 #define Maxn 110
  9 #define Maxm 10010
 10 #define INF 0xfffffff
 11 
 12 int n,m,mc;
 13 
 14 struct node
 15 {
 16     int x,y,b,c;
 17 };
 18 node t[Maxm],tt[Maxm];
 19 
 20 bool cmp(node x,node y) {return x.c<y.c;}
 21 int mymin(int x,int y) {return x<y?x:y;}
 22 int mymax(int x,int y) {return x>y?x:y;}
 23 
 24 int fa[Maxn];
 25 
 26 int ffa(int x)
 27 {
 28     if(fa[x]!=x) fa[x]=ffa(fa[x]);
 29     return fa[x];
 30 }
 31 
 32 int in[Maxn],pre[Maxn],id[Maxn],vis[Maxn];
 33 //in -> 最小入边 边权
 34 //pre -> 最小入边的发出点
 35 //id -> 所在圈的编号
 36 //vis 所在链顶端
 37 
 38 int rt;
 39 
 40 bool check(int mid) //MDST
 41 {
 42     rt=1;
 43     int ans=0;
 44     int nn=n;
 45     for(int i=1;i<=m;i++) tt[i]=t[i];
 46     while(1)
 47     {
 48         //1、找最小入边
 49         for(int i=1;i<=nn;i++) in[i]=INF;
 50         for(int i=1;i<=m;i++) 
 51         {
 52             int x=tt[i].x,y=tt[i].y;
 53             if(tt[i].c<in[y]&&x!=y&&tt[i].b>=mid) //因为缩边,所以可能有自环
 54             {
 55                 pre[y]=x;
 56                 in[y]=tt[i].c;
 57             }
 58         }
 59         //2、判断是否有最小生成树
 60         for(int i=1;i<=nn;i++) if(i!=rt)
 61         {
 62             if(in[i]==INF) return 0;
 63         }
 64         //3、找环
 65         int cnt=0;
 66         memset(id,-1,sizeof(id));
 67         memset(vis,-1,sizeof(vis));
 68         in[rt]=0;
 69         for(int i=1;i<=nn;i++)
 70         {
 71             ans+=in[i];
 72             int now=i;
 73             while(vis[now]!=i&&id[now]==-1&&now!=rt)
 74             {
 75                 vis[now]=i;
 76                 now=pre[now];
 77             }
 78             if(now!=rt&&id[now]==-1)
 79             {
 80                 cnt++;
 81                 for(int j=pre[now];j!=now;j=pre[j])
 82                 {
 83                     id[j]=cnt;
 84                 }
 85                 id[now]=cnt;
 86             }
 87         }
 88         if(cnt==0) break;//没有圈啦233
 89         for(int i=1;i<=nn;i++) if(id[i]==-1)
 90             id[i]=++cnt; //相当于自己一个圈
 91         //4、缩点,重新标记
 92         for(int i=1;i<=m;i++)
 93         {
 94             int x=tt[i].x,y=tt[i].y;
 95             tt[i].x=id[x];tt[i].y=id[y];
 96             if(tt[i].x!=tt[i].y)
 97             {
 98                 tt[i].c-=in[y];
 99             }
100         }
101         nn=cnt;
102         rt=id[rt];
103     }
104     return ans<=mc;
105 }
106 
107 void ffind(int l,int r)
108 {
109     int ans=-1;
110     while(l<=r)
111     {
112         int mid=(l+r)>>1;
113         if(check(mid)) ans=mid,l=mid+1;
114         else r=mid-1;
115     }
116     if(ans==-1) printf("streaming not possible.
");
117     else printf("%d kbps
",ans);
118 }
119 
120 int main()
121 {
122     int T;
123     scanf("%d",&T);
124     while(T--)
125     {
126         scanf("%d%d%d",&n,&m,&mc);
127         int mn=INF,mx=0;
128         for(int i=1;i<=m;i++)
129         {
130             scanf("%d%d%d%d",&t[i].x,&t[i].y,&t[i].b,&t[i].c);
131             t[i].x++;t[i].y++;
132             mn=mymin(mn,t[i].b);
133             mx=mymax(mx,t[i].b);
134         }
135         sort(t+1,t+1+m,cmp);
136         ffind(mn,mx);
137     }
138     return 0; 
139 }
View Code

 // vis相当于一个不清0的bool

2016-11-01 10:43:31


记录一下MDST做法,求有向最小生成树,也叫最小树形图。

主过程:

1、给所有非根节点选一条权最小的入边。

2、如果有一个点没有入边,那么无解。

3、判断选出来的点是否构成圈,给每个点打上他所在的圈的编号标记(一定是i一个简单环,因为每个点只选一条入边)

4、缩点,求出新的图。

注意:

第4步,入边的权值不是单纯的原图权值,还要减去圈里面和他矛盾的边的权值。

时间复杂度:O(VE)

如果一开始没有给你根怎么破???????????

如果是不定根的情况我们可以虚拟一个根,让虚拟根到每个节点的距离为图上所有边的权值之和加一。这样找到最小树形图后再减掉所有边的权值之和加一就可以了。


搜刮网上MDST的解释:

 

    图论是ACM竞赛中比较重要的组成部分,其模型广泛存在于现实生活之中。因其表述形象生动,思维方式抽象而不离具体,因此深受各类喜欢使劲YYAcmer的喜爱。这篇文章引述图论中有关有向图最小生成树的部分,具体介绍朱刘算法的求解思路,并结合一系列coding技巧,实现最小树型图OVE)的算法,并在最后提供了该算法的模版,以供参考。

  关于最小生成树的概念,想必已然家喻户晓。给定一个连通图,要求得到一个包含所有顶点的树(原图的子图),使之所构成的边权值之和最小。在无向图中,由于边的无向性质,所以求解变得十分容易,使用经典的kruskalprim算法已经足够解决这类问题。而在有向图中,则定义要稍微加上一点约束,即以根为起点,沿给定有向边,可以访问到所有的点,并使所构成的边权值之和最小,于是问题开始变得不一样了,我们称之为最小树型图问题。

  该问题是由朱永津与刘振宏在上个世纪60年代解决的,值得一提的是,这2个人现在仍然健在,更另人敬佩的是,朱永津几乎可以说是一位盲人。解决最小树型图的算法后来被称作朱刘算法,也是当之无愧的。

  首先我们来阐述下算法的流程:算法一开始先判断从固定根开始是否可达所有原图中的点,若不可,则一定不存在最小树形图。这一步是一个很随便的搜索,写多搓都行,不加废话。第二步,遍历所有的边,从中找出除根结点外各点的最小入边,累加权值,构成新图。接着判断该图是否存在环。若不存在,则该图便是所求最小树型图,当前权为最小权。否则对环缩点,然后回到第二步继续判断。

  这里存在一系列细节上的实现问题,以确保能够达到VE的复杂度。首先是查环,对于新图来说只有n-1条入边,对于各条入边,其指向的顶点是唯一的,于是我们可以在边表中添加from,表示该边的出发点,并考虑到如果存在环,则对环上所有边反向,环是显然同构的,于是最多作Vdfs就能在新图中找到所有的环,并能在递归返回时对顶点重标号进行缩点,此步的重标号可以用hash数组映射。然后就是重要的改边法,对于所有不在环上的边,修改其权为w-min(v)w为当前边权,min(v)为当前连接v点的最小边权。其数学意义在于选择当前边的同时便放弃了原来的最小入边。我们可以知道,每次迭代选边操作O(E),缩点操作O(V),更新权操作O(E),至少使一个顶点归入生成树,于是能在V-1步内完成计算,其复杂度为O(VE)

以上为定根最小树型图,对于无固定根最小树型图,只要虚拟一个根连所有的点的权为边权总和+1,最后的结果减去(边权+1)即可。另外对于求所定的根标号,只要搜索被选中的虚边就可以判断了。

 1 #include <cstdio>
 2 #include <iostream>
 3 #include <cmath>
 4 #include <cstring>
 5 
 6 using namespace std;
 7 
 8 const int N=101,M=10001,inf=2147483647;
 9 struct edge {int u,v,w;} e[M];
10 int n,m,ans,id[N],pre[N],v[N],inw[N];
11 
12 void zhu_liu(int root)
13   {
14     int s,t,idx=n;
15     while (idx)
16       {
17         for (int i=1;i<=n;++i) inw[i]=inf,id[i]=-1,v[i]=-1;
18         for (int i=1;i<=m;++i)
19           {
20             s=e[i].u;t=e[i].v;
21             if (e[i].w>inw[t] || s==t) continue;
22             pre[t]=s;
23             inw[t]=e[i].w;
24           }
25         inw[root]=0;pre[root]=root;
26         for (int i=1;i<=n;++i)
27           {
28             if (inw[i]==inf)
29               {
30                 printf("impossible
");
31                 return;
32               }
33             ans+=inw[i];
34           }
35         idx=0;
36         for (int i=1;i<=n;++i)
37           if (v[i]==-1)
38             {
39               t=i;
40               while (v[t]==-1) v[t]=i,t=pre[t];
41               if (v[t]!=i || t==root) continue;
42               id[t]=++idx;
43               for (s=pre[t];s!=t;s=pre[s]) id[s]=idx;
44             }
45         if (idx==0) continue;
46         for (int i=1;i<=n;++i)
47           if (id[i]==-1) id[i]=++idx;
48         for (int i=1;i<=m;++i)
49           {
50             e[i].w-=inw[e[i].v];
51             e[i].u=id[e[i].u];
52             e[i].v=id[e[i].v];
53           }
54         n=idx;
55         root=id[root];
56       }
57     printf("%d
",ans);
58   }
59 int main()
60   {
61     scanf("%d%d",&n,&m);
62     for (int i=1;i<=m;++i)
63       scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
64     zhu_liu(1);
65     return 0;
66   }
View Code

 

 
原文地址:https://www.cnblogs.com/Konjakmoyu/p/6018626.html