对朱刘算法求最小树形图的理解(uva11865)

题目大意

一个图,0号节点是源点,每一条单向边有一个价值v和一个费用w,你只有C元钱,要使得源点和所有点都连通,且价值最小的边的价值最大。

题目分析

二分最小价值,只有比最小价值大的价值的边才能够加进去+最小树形图
最小树形图是什么呢?除了根节点以外,每一个点都有一个入度,且根节点可以到达所有节点的神奇图叫做树形图,而最小树形图,当然就是边权和最小咯!。

朱刘算法

步骤1

操作内容:贪心,给除了根节点以外的每一个点都找一个前驱节点,其中前驱节点到该节点的边是尽可能小的。如果某一个点没有入度,那么肯定就不存在树形图咯!

for(i=1;i<=m;++i){
            if(e[i].v>=lim&&e[i].x!=e[i].y&&e[i].w<in[e[i].y])
            in[e[i].y]=e[i].w,pre[e[i].y]=e[i].x;
        }
        for(i=1;i<=num;++i)if(!pre[i]&&i!=rt)return 0;
        in[rt]=0,js=0;

步骤2

寻找简单环,因为我们已经找出前驱节点了,所以我们顺着前驱节点找简单环就可以了。如果没有简单环,那么就皆大欢喜,找到解了。如果有简单环呢?我们的目标是拆掉这个环中的一条边,然后连另一条入边进入这个点。这个方法可以用贪心的思想感受一下……
然后给每个简单环标号,以后它们是要被缩成一个点的。

for(i=1;i<=num;++i){
            re+=in[i],t1=i;//re+=in[i]:这个计算答案的方法在步骤3有讲解
            while(t1!=rt&&vis[t1]!=i&&!id[t1])vis[t1]=i,t1=pre[t1];
            if(t1!=rt&&!id[t1]){//在t1处找到了简单环
                t2=pre[t1],++js;
                while(t2!=t1)id[t2]=js,t2=pre[t2];
                id[t1]=js;//标号
            }
        }
        if(!js){return re<=c;}//没有简单环,即找到结论
        for(i=1;i<=num;++i)if(!id[i])id[i]=++js;

步骤3

缩点。
所有简单环看作一个点x,然后对于从环里走出的出边,就是x向外的出边,不用改变边权。对于入边,如果环外一点y到环内一点z之间有一条边权为w1的边,而如上面代码,in[y]=w2,则新边改成y到x的边,边权为w1-w2
为什么这么搞事呢?因为大家肯定注意到上面代码有这么一句:

re+=in[i];

所以说,我们这样只是减去原来选择的边造成的贡献,加上新边造成的贡献而已。
为了方便理解,给图如下:
图1
图2

for(i=1;i<=m;++i)if(e[i].v>=lim){
            int kl=e[i].y;e[i].x=id[e[i].x],e[i].y=id[e[i].y];
            if(e[i].x!=e[i].y)e[i].w-=in[kl];
        }
        rt=id[rt],num=js;

分段的代码可能有一些变量名没有解释清楚,那么看完整版代码吧。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<climits>
#include<cmath>
using namespace std;
#define LL long long
const int M=10005,N=65;
int T,n,m,c;
struct node{int x,y,v,w;}e[M],ee[M];
int pre[N],vis[N],id[N],in[N];
int ok(int lim){
    int re=0,i,j,num=n,rt=1,js,t1,t2;
    for(i=1;i<=m;++i)e[i]=ee[i];
    while(1){
        for(i=1;i<=num;++i)pre[i]=id[i]=vis[i]=0,in[i]=1e9;
        for(i=1;i<=m;++i){
            if(e[i].v>=lim&&e[i].x!=e[i].y&&e[i].w<in[e[i].y])//e[i].x!=e[i].y别忘了
            in[e[i].y]=e[i].w,pre[e[i].y]=e[i].x;
        }
        for(i=1;i<=num;++i)if(!pre[i]&&i!=rt)return 0;
        in[rt]=0,js=0;//步骤1
        for(i=1;i<=num;++i){
            re+=in[i],t1=i;
            while(t1!=rt&&vis[t1]!=i&&!id[t1])vis[t1]=i,t1=pre[t1];
            if(t1!=rt&&!id[t1]){//t1!
                t2=pre[t1],++js;
                while(t2!=t1)id[t2]=js,t2=pre[t2];
                id[t1]=js;
            }
        }
        if(!js){return re<=c;}
        for(i=1;i<=num;++i)if(!id[i])id[i]=++js;//步骤2
        for(i=1;i<=m;++i)if(e[i].v>=lim){//别忘了这句
            int kl=e[i].y;e[i].x=id[e[i].x],e[i].y=id[e[i].y];
            if(e[i].x!=e[i].y)e[i].w-=in[kl];
        }
        rt=id[rt],num=js;//步骤3
    }
}
int main(){
    int i,j,l,r,mid,ans;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d%d",&n,&m,&c);
        l=0,r=0,ans=-1;
        for(i=1;i<=m;++i){
            scanf("%d%d%d%d",&ee[i].x,&ee[i].y,&ee[i].v,&ee[i].w);
            ++ee[i].x,++ee[i].y;r=max(r,ee[i].v);
        }
        while(l<=r){//二分答案
            mid=(l+r)>>1;
            if(ok(mid))ans=mid,l=mid+1;
            else r=mid-1;
        }
        if(ans==-1)printf("streaming not possible.\n");
        else printf("%d kbps\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值