题意:需要用不超过cost元来建立一个能到所有点的网络,每条边都有花费和带宽,要求一颗能到所有点的树,使得整棵树的最小带宽(即该图中所有点的带宽的最小值)最大。
解析:如果已知最小带宽了,问题转化为:如果仅用小于此带宽的网线,是否可以再给定花费内成功搭建网络,则明显应该求最小生成树,因为是有向图,所以就是最小树形图,可以用朱刘算法来求。朱刘算法详解:http://blog.sina.com.cn/s/blog_6af663940100ls4h.html
我的代码里注释还是挺详细的,如果还有不懂得话,大家可以自己画个图模拟一下过程就行了
然后可以二分最小带宽,删去图中比最小带宽的带宽还小的边。
当最小带宽越小,删除的边就越少,就越容易构成最小树形图。因为要求最小带宽的最大值,所以就可以二分这个最小带宽
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define MEM(x,y) memset(x,y,sizeof(x))
#define Pmax 100
#define Emax 11000
#define Max 1000000000
struct edge
{
int from,to;
int len; //长度
int band; //带宽
}e[5*Emax];
int pre[Pmax],vis[Pmax]; //pre[]用来存点的前驱点,vis[]用来记录在最小树形图中该点是由那个点遍历得到的
int id[Pmax]; //id[]用来存缩环之后点合并的坐标
int in[Pmax]; //in[]存进入该点的最短入边
int N,M,C;
int cnt;
void addedge(int a,int b,int c,int d) //x代表从哪开始存
{
e[cnt].band = c;
e[cnt].from = a;
e[cnt].to = b;
e[cnt].len = d;
e[cnt+M].band = c; //因为每一次在Directed_MST里都会改变e[],所以把最初的存在M->2*M-1里
e[cnt+M].from = a;
e[cnt+M].to = b;
e[cnt+M].len = d;
cnt++;
}
bool Directed_MST(int root ,int Band,int n) //Band是这次所求的最小带宽,n是点的个数
{
long long ans = 0;
while(1)
{
//求每个点的最小入边
MEM(pre,-1);
for(int i = 0 ; i < n ; i ++)
in[i] = Max;
for(int i = 0 ; i < M; i++)
{
int from = e[i].from;
int to = e[i].to;
if(from != to && e[i].band >= Band && e[i].len < in[to]) //3个条件:没有自环;大于最小带宽;求in[i]最小值
{
in[to] = e[i].len;
pre[to] = from;
}
}
//判断有没有为树
for(int i = 0 ; i < n;i++)
{
if(i == root)
continue;
if(in[i]==Max) //代表有的点没到,即不可能是最小树形图了
return false;
}
//找到环
int subnode = 0; //用来记录缩环后的环的替代点
MEM(vis,-1);
MEM(id,-1);
in[root] = 0; //根节点没有入边,所以边权为0
for(int i = 0 ; i < n;i++)
{
ans += in[i]; //注意此时加上所有的点的入边权值,在后面有相应的对应
int temp = i;
while(vis[temp] != i && id[temp] == -1 && temp != root)
{
vis[temp] = i; //temp由i点遍历得到
temp = pre[temp];
}
//i点向父节点遍历如果有环的话,temp肯定是环中的某个点,不一定是i点
if(temp != root && id[temp] == -1)
{
int u = pre[temp];
for( ;u != temp; u = pre[u])
id[u] = subnode; //同一个环中的所有点都是用这个替代点表示
id[temp] = subnode;
subnode++;
}
}
if(subnode == 0) //代表没有环了,也就是说现在已经是最小树形图了
break;
for(int i = 0 ; i < n; i ++)
if(id[i] == -1) //对于不在环中的点,也给他们重新找替代点
id[i] = subnode++;
//缩环,重新标记
for(int i = 0 ; i < M ; i ++)
{
int temp = e[i].to;
e[i].from = id[e[i].from]; //全部更新为新的替代点
e[i].to = id[e[i].to];
if(e[i].from != e[i].to) //如果不是在同一个环内的话,就要减少边长
e[i].len -= in[temp]; //对于指向环的边,这么减没问题; 对于从环内点指向外面的边
//之所以这么减,是因为上面ans已经把所有点的入点边权都加上了,对应上面
//也就是说如果ans表示的仅仅是环内的边权话,那么就只是入边减,出边不减了
}
root = id[root]; //最后更新根节点和点的个数
n = subnode;
}
if(ans <= C)
return true;
else
return false;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d%d",&N,&M,&C);
int maxb = 0,minb = Max; //用来存最大最小带宽,后面二分用
cnt = 0;
for(int i = 1; i <= M ; i ++)
{
int a,b,c,d;
scanf("%d%d%d%d",&a,&b,&c,&d);
addedge(a,b,c,d);
maxb = max(maxb,c);
minb = min(minb,c);
}
if(Directed_MST(0,minb,N) == false) //如果最小带宽取最小值时(即图中的每条边都没删)都不能构成最小树形图
printf("streaming not possible.\n");
else
{
int l = minb,r = maxb;
int mid;
while(l<r) //二分过程
{
mid = l+(r-l+1)/2;
for(int i = 0 ; i < M; i ++)
e[i] = e[i+M];
if(Directed_MST(0,mid,N) == true) //如果最小带宽满足,就可以取更大的带宽
l = mid;
else
r = mid-1;
}
printf("%d kbps\n",r);
}
}
return 0;
}