kuangbin专题八 HDU2121 Ice_cream’s world II(不定根的最小树形图)

本文介绍了一种使用超级源点解决不定根最小树形图问题的方法,通过添加超级源点并调整边的权值,确保算法能够正确地找到合适的首都城市,使总路程最短,并能返回最小的城市编号。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:
一个国家有N个城市,M条有向道路,女王想要选一个城市为首都,使得这个城市既能连接所有的城市,而且总的路程最短。若能找到这个城市,则输出最短路程和最小的城市编号。
题解:
最小树形图+超级源点,因为是不定根,我一开始是想暴力枚举的,然后发现复杂度爆炸了,就放弃了,后来才知道这道题可以用超级源点,怎么用超级源点呢?如果要用超级源点的话,肯定得在用完它之后删除掉这个点和与它有关的边之后对图没有影响,如何做到呢?我们可以把整个图的边的权值全部加起来得出sum,然后sum+1,因为要大于整个图的权值删除之后才不会影响原图。把超级源点定为n,因为图中的点是从0到n-1的。然后再把超级源点与其他点联系起来,权值为sum,这时候图中的边为m+n了。之后算出结果之后减去sum就可以了,如果发现得出的值比sum*2还要大,说明超级源点连上了原图中的两个点,这样是不对的。然后最小的城市编号怎么求呢?因为会缩点,所以感觉很难求,后来看了大佬的讲解之后发现可以利用最小边的性质,在有解的情况下,只会出现一次超级源点与图中某个点相连起来的情况,为什么?这时候sum的作用就出来了,因为sum比图中任何的边都大,而且原图的边树m是在后面加上的超级源点的n条边的前面的,算法是先进行这m条边的最小入边再进行超级源点的边的,所以如果在有解的情况下图中肯定只会出现一次超级源点和图中某点的相连,不会出现第二次,如果出现第二次说明这个超级源点与图中两个点相连,则无解。然后为什么是看边,不会在点中找根呢?为什么最小树形图会缩点啊,这样不好找,而在边上找就好的多了,之后在找到边的编号之后-m就可以了,为什么是-m就得出结果,因为后面那些边在m后面的n条边里面的。接下来看代码理解。
题外话:

原谅我脑子太笨。。。只会模板,这道题我是真的想不到要用超级源点,明明我之前就做过超级源点的题。。而且对最小树形图的算法的理解也不够深刻,哎,能力有待提升。       
//解决不定根的经典方法:加超级源点。
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
const int MAXN=1000+7;
struct node
{
    int u,v,w; 
}map[40000+7];
int pre[MAXN];
int IN[MAXN];
int ID[MAXN];
int vis[MAXN];
int n,m;
int sum,pos;
int zhuliu_mst(int root,int V,int E)
{
    int res=0;
    while(true)
    {
        //1.找最小入边
        for(int i=0;i<V;i++) IN[i]=INF;
        for(int i=0;i<E;i++)
        {
            int u=map[i].u;
            int v=map[i].v;
            if(map[i].w<IN[v]&&u!=v)
            {
                pre[v]=u;
                IN[v]=map[i].w;
                if(u==root)//就多了这两行,其他都一样,只要找到(root,v,w)这条边就可以知道最小的根了,root为超级源点。 
                pos=i;//对的情况下是只会出现一次的,因为是最小入边的性质确定的。 
            }
        }
        for(int i=0;i<V;i++)
        {
            if(i==root) continue;
            if(IN[i]==INF) return -1;
        }
        int cnt=0;
        memset(ID,-1,sizeof(ID));
        memset(vis,-1,sizeof(vis));
        IN[root]=0;
        for(int i=0;i<V;i++)//标记每个环 
        {
            res+=IN[i];
            int v=i;
            while(vis[v]!=i&&ID[v]==-1&&v!=root)
            {
                vis[v]=i;
                v=pre[v];
            }
            if(v!=root&&ID[v]==-1)
            {
                for(int u=pre[v];u!=v;u=pre[u])
                {
                    ID[u]=cnt;
                }
                ID[v]=cnt++;
            }
        }
        if(cnt==0) break;//无环 
        for(int i=0;i<V;i++)
        {
            if(ID[i]==-1)
            ID[i]=cnt++;
        }
        //3.缩点,重新标记
        for(int i=0;i<E;i++)
        {
            int u=map[i].u;
            int v=map[i].v;
            map[i].u=ID[u];
            map[i].v=ID[v];
            if(ID[u]!=ID[v])
            {
                map[i].w-=IN[v];
            }
        }
        V=cnt;
        root=ID[root];
    }
    return res;
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        sum=0;
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d",&map[i].u,&map[i].v,&map[i].w);
            if(map[i].u==map[i].v)
            map[i].w=INF;
            else
            sum+=map[i].w;
        }
        sum++;
        for(int i=0;i<n;i++)
        {
            map[i+m].u=n;
            map[i+m].v=i;
            map[i+m].w=sum;
        }
        int ans=zhuliu_mst(n,n+1,m+n);
        if(ans>=sum*2||ans==-1)
        printf("impossible\n\n");
        else
        printf("%d %d\n\n",ans-sum,pos-m);
    }
}#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
const int MAXN=1000+7;
struct node
{
    int u,v,w; 
}map[40000+7];
int pre[MAXN];
int IN[MAXN];
int ID[MAXN];
int vis[MAXN];
int n,m;
int sum,pos;
int zhuliu_mst(int root,int V,int E)
{
    int res=0;
    while(true)
    {
        //1.找最小入边
        for(int i=0;i<V;i++) IN[i]=INF;
        for(int i=0;i<E;i++)
        {
            int u=map[i].u;
            int v=map[i].v;
            if(map[i].w<IN[v]&&u!=v)
            {
                pre[v]=u;
                IN[v]=map[i].w;
                if(u==root)//就多了这两行,其他都一样,只要找到(root,v,w)这条边就可以知道最小的根了,root为超级源点。 
                pos=i;//对的情况下是只会出现一次的,因为是最小入边的性质确定的。 
            }
        }
        for(int i=0;i<V;i++)
        {
            if(i==root) continue;
            if(IN[i]==INF) return -1;
        }
        int cnt=0;
        memset(ID,-1,sizeof(ID));
        memset(vis,-1,sizeof(vis));
        IN[root]=0;
        for(int i=0;i<V;i++)//标记每个环 
        {
            res+=IN[i];
            int v=i;
            while(vis[v]!=i&&ID[v]==-1&&v!=root)
            {
                vis[v]=i;
                v=pre[v];
            }
            if(v!=root&&ID[v]==-1)
            {
                for(int u=pre[v];u!=v;u=pre[u])
                {
                    ID[u]=cnt;
                }
                ID[v]=cnt++;
            }
        }
        if(cnt==0) break;//无环 
        for(int i=0;i<V;i++)
        {
            if(ID[i]==-1)
            ID[i]=cnt++;
        }
        //3.缩点,重新标记
        for(int i=0;i<E;i++)
        {
            int u=map[i].u;
            int v=map[i].v;
            map[i].u=ID[u];
            map[i].v=ID[v];
            if(ID[u]!=ID[v])
            {
                map[i].w-=IN[v];
            }
        }
        V=cnt;
        root=ID[root];
    }
    return res;
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        sum=0;
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d",&map[i].u,&map[i].v,&map[i].w);
            if(map[i].u==map[i].v)
            map[i].w=INF;
            else
            sum+=map[i].w;
        }
        sum++;
        for(int i=0;i<n;i++)
        {
            map[i+m].u=n;
            map[i+m].v=i;
            map[i+m].w=sum;
        }
        int ans=zhuliu_mst(n,n+1,m+n);
        if(ans>=sum*2||ans==-1)
        printf("impossible\n\n");
        else
        printf("%d %d\n\n",ans-sum,pos-m);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值