返回顶部

【朱刘算法】最小树形图

最小树形图

1 DAG的最小树形图

一个树形图中,除了r点其他点都只有一个入边

那么对于一个DAG,只要我们对于每一个点选出最小的入边,那么这一定是个树形图

那么算法就非常的简单

2 环的最小树形图

这个问题十分傻,显然,从r开始绕一圈少一条边就行了

3 真正的最小树形图

这个东西叫做朱刘算法

img

1)建图原理

先用DAG的算法找出每个点的最小入边记为红边,其他边记为蓝边。黄色部分是红边形成的环。

如果黄色的环不存在,则我们直接得到了最小树形图,直接输出。

那么如果是上面这种情况。如果存在环,那么这个环内的点不会被外部的点连接,因为每个点只有一个入度,全被环内占据了。

而我们要得到最小树形图,就需要把其中一条边换成环外边

将环缩成一个点,环外边指向该点。

为了方便统计,由于我们确信环外边的长度 \(\ge\) 环内边的长度,那么我们ans先加上这个环的边权和,然后每一条环外边 \(E_u\) ,其权值 \(val_u=val_u-val_{nei}\) ,其中 \(E_{nei}\) 是该点的最小入边。

用那张图解释一下:

img (引用一下别人的别人的别人的题解的图,这张图实在太好用了,放这里做参考)

这样有什么好处?

这样相当于进行本质不变的图形转化,使得我们可以把环缩成一个点。环上的边相当于占据了外部边的部分边权,将这个边权从外部边上减去,此时环再与外部边相连,就等同于舍弃了环内一边,连上环外一边,而答案就只需要加上外部边剩下的边权。同时,这样环本质上就变成了一个独立的点,将原图刷新后得到一张新的图,和原图本质不变,但减少了环。就这样不断地用新图重复上述找环操作,最后的图一定是DAG,得到的答案即为DAG的答案。

2)如何找环

对于一个点 \(u\) ,设其红色入边的出发点\(fa[u]\)

再设 \(u\) 最早的祖宗(\(u\) 的最早前驱) 为 \(tp[u]\)

遍历每个 \(u\) ,然后让 \(u\) 沿着 \(fa\) 逆向走边,直到或者前驱是自己的点

1.如果走到根,那么这个点不在环上;

2.如果 \(u\) 走到了某个 \(v\), 使得 \(tp[v]==u\) ,那么 \(v\) 就是一个环上的点。

为方便,设 \(lp[u]\) 表示 \(u\) 所属的环,若 其为0,代表该点还没有所属的环,设置新环 \(lp[u]=++cnt\) ,环上每个点都要标记。

for(int u=1,v=1;u<=n;u++,v=u)
{
    while(v!=root &&tp[v]!=u && !lp[v])
    {
        tp[v]=u, v=fa[v];//v是u向上逆行的fa
    }//退出循环代表 tp[v]==u,u的祖先的前驱是u,那就形成了环
    if(v!=root && !lp[v])
    {
        lp[v]=++cnt;
        for(int k=fa[v];k!=v;k=fa[k]) lp[k]=cnt;
    }//环上每个点打标记
}

3)何时结束

一但没有环,就是最小树形图。

4)实际操作

每一个循环中,找到每个点的红边,再判断是否成环,将环的外部边边权刷新,最后把环当点建新图,重复循环直到没有环存在

算法流程

对每一次循环:

(1).贪心找红边,记录入边的起点(父亲)

(2).把所有红边加入ans,若一点没有红边,则图不连通,输出false

(3).找环

(4).没有环,直接退出

(5).所有不是环的点设为大小为1的独立环

(6).原先的图转化为现在缩点的图

(7).对新图重复以上操作

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=107,maxm=1e4+7;
int n,m,ans,root;
int fa[maxn],tp[maxn],lp[maxn],mn[maxn];
int cnt=0;
struct edge{
    int u,v,w;
}e[maxm];


int zl()//朱刘算法
{
    while(true)
    {
        cnt=0;
        for(int i=1;i<=n;++i) mn[i]=1e9,fa[i]=tp[i]=lp[i]=0;
        //初始化
        for(int i=1,v,w;i<=m;++i)//(1)
            if(e[i].u!=e[i].v && (w=e[i].w)<mn[v=e[i].v])
            {
                mn[v]=w; fa[v]=e[i].u;
            }//遍历所有边,找红边
        mn[root]=0;
        
        for(int u=1;u<=n;++u)//(2)
        {
            ans+=mn[u];
            if(mn[u]==1e9) return -1;//图不连通,输出false
        }
        
        for(int u=1,v=1;u<=n;++u,v=u)//(3)
        {
            while(v!=root && tp[v]!=u && !lp[v])
            {//根节点没有入边,所以root不可能成环
                tp[v]=u; v=fa[v];
            }
            if(v!=root && !lp[v])
            {
                lp[v]=++cnt;
                for(int k=fa[v];k!=v;k=fa[k])
                {
                    lp[k]=cnt;
                }
            }
        }
        
        if(!cnt) return ans;//(4)没有环了,输出答案
        
        for(int u=1;u<=n;++u) if(!lp[u]) lp[u]=++cnt;//(5)
        
        for(int i=1;i<=m;++i)//(6)
        {
            e[i].w -= mn[e[i].v];//所有红边都计入答案,外部点改边权去重
            e[i].u = lp[e[i].u];//原边关系转化为缩点后环的关系
            e[i].v = lp[e[i].v];//同上,建新图
        }
        n=cnt, root = lp[root];//新图的n和根
    }//(7)
}
int main(){
	scanf("%d%d%d",&n,&m,&root);
	for(int i=1,u,v,w;i<=m;i++)
		scanf("%d%d%d",&u,&v,&w),e[i]=(edge){u,v,w};
	printf("%d",zl());
	return 0;
}

极致简约版

#include<iostream>
using namespace std;
enum{maxn=107,maxm=10007};
int n,m,ans,root,fa[maxn],tp[maxn],lp[maxn],mn[maxn],cnt;
struct edge{int u,v,w;}e[maxm];
int zl(){      
        for(;true;) {
        for(int i=1;i<=n;++i) mn[i]=1e9,fa[i]=tp[i]=lp[i]=0;
        for(int i=1,v,w;i<=m;++i)
            if(e[i].u!=e[i].v && (w=e[i].w)<mn[v=e[i].v])  mn[v]=w,fa[v]=e[i].u;
        mn[root]=0;
        for(int u=1;u<=n;++u) {ans+=mn[u]; if(mn[u]==1e9) return -1;}
        for(int u=1,v=1;u<=n;++u,v=u){
            while(v!=root && tp[v]!=u && !lp[v]) {tp[v]=u,v=fa[v];}
            if(v!=root && !lp[v]) { lp[v]=++cnt; for(int k=fa[v];k!=v;k=fa[k]) lp[k]=cnt;}
        }
        if(!cnt) return ans;
        for(int u=1;u<=n;++u) if(!lp[u]) lp[u]=++cnt; 
        for(int i=1;i<=m;++i) {e[i].w -= mn[e[i].v],e[i].u = lp[e[i].u],e[i].v = lp[e[i].v];}
        n=cnt,root=lp[root],cnt=0;
    }
}
int main(){ios::sync_with_stdio(0);cin>>n>>m>>root;
	for(int i=1,u,v,w;i<=m;i++) cin>>u>>v>>w,e[i]=(edge){u,v,w};
	return cout<<zl(),0;
}
posted @ 2022-06-29 21:25  魔幻世界魔幻人生  阅读(66)  评论(0)    收藏  举报