• 洛谷 题解 UVA1151 【买还是建 Buy or Build】


    【题意】

    平面上有(n(n<=1000))个点,你的任务是让所有n个点联通。为此,你可以新建一些边,费用等于两个端点的欧几里得距离平方。另外还有(q(q<=8))个套餐可以购买,如果你购买了第(i)个套餐,该套餐中的所有结点将变得相互连接。第(i)个套餐的花费为(C_i)

    【算法】

    (Kruskal)

    【分析】

    最容易想到的算法是:先枚举购买哪些套餐,把套餐中包含的权值设为(0),然后求最小生成树。由于枚举量为(O(2^q)),给边排序的时间复杂度为(O(n^2logn)),而排序之后每次(kruskal)算法的时间复杂度为(O(n^2)),因此总时间复杂度为(O(2^qn^2+n^2logn)),对于题目来说的规模太大了。

    只需一个小小的优化即可降低时间复杂度:先求一次原图 (不购买任何套餐) 的最小生成树,得到(n-1)条边,其余的边就没用了。然后枚举买哪些套餐(这里可以用状态压缩的思想),则枚举套餐后再求最小生成树时,图上的边已经寥寥无几。

    为什么可以这样呢? 大部分题解都没有证明。这里给出证明过程

    首先回顾一下,在(kruskal)算法中,哪些边不会进入最小生成树。答案是:两端已经属于同一个集合的边。买了套餐后,相当于一些边的边权变成了(0),而对于不在套餐中的每条边(e),排序在(e)之前的边一个都没少,反而还多了一些权值为(0)的边,所以在原图(kruskal)时被“扔掉”的边,在后面的枚举套餐中也一样会被扔掉。

    【代码】

    #include<bits/stdc++.h>
    using namespace std;
    const int MAXN=1000+10;
    const int MAXM=MAXN*MAXN;
    int n,q,T,ans=0x3f3f3f3f;
    int s[10][MAXN];
    int c[10];
    struct Node2
    {
        int x,y;
    }city[MAXN];
    struct Node
    {
        int u,v,w;
    }edge[MAXM],g[MAXM];
    int cnt,m;
    int fa[MAXN];
    int save[MAXN];
    inline int read()
    {
        int tot=0;
        char c=getchar();
        while(c<'0'||c>'9')
            c=getchar();
        while(c>='0'&&c<='9')
        {
            tot=tot*10+c-'0';
            c=getchar();
        }
        return tot;
    }
    inline bool cmp(Node x,Node y)
    {
        return x.w<y.w;
    }
    inline int find(int k)
    {
        if(fa[k]==k)return k;
        else return fa[k]=find(fa[k]);
    }
    inline int init_kruskal()
    {
        int tot=0,cc=0;
        for(int i=1;i<=n;i++)
            fa[i]=i;
        for(int i=1;i<=cnt;i++)
        {
            if(fa[find(edge[i].u)]!=fa[find(edge[i].v)])
            {
                fa[find(edge[i].u)]=find(edge[i].v);
                tot++;
                cc+=edge[i].w;
                save[tot]=i;//记录边
            }
            if(tot==n-1)break;
        }
        return cc;
    }
    inline int kruskal(int tot)
    {
        int cc=0,t=tot;
        for(int i=1;i<n;i++)
        {
            if(find(g[i].u)!=find(g[i].v))
            {
                fa[find(g[i].u)]=find(g[i].v);
                t++;
                cc+=g[i].w;
            }
            if(t==n-1)break;
        }
        return cc;
    }
    inline void solve()
    {
        for(int ss=0;ss<(1<<q);ss++)//状压思想,用二进制来表示选还是不选
        {
            for(int i=1;i<=n;i++)
            fa[i]=i;//初始化并查集
            int tot=0;//选中套餐中被连接的点数
            int cc=0;//套餐的钱
            for(int k=1;k<=q;k++)
            {
                if(ss&(1<<(k-1)))//如该套餐被选中
                {
                    //cout<<k<<" ";
                    cc+=c[k];
                    for(int i=1;i<=s[k][0];i++)
                    {
                        for(int j=i+1;j<=s[k][0];j++)
                        {
                            //cout<<s[k][0]<<" "<<k<<" "<<s[k][i]<<" "<<s[k][j]<<endl;
                            if(find(s[k][i])!=find(s[k][j]))
                            {
                                fa[find(s[k][i])]=find(s[k][j]);
                                tot++;
                            }
                        }
                    }
                }
            }
            //cout<<endl;
            //cout<<cc<<endl;
            //cout<<tot<<" "<<kruskal(tot)<<" "<<cc<<endl;
            ans=min(ans,kruskal(tot)+cc);//更新最小值
        }
    }
    int main()
    {
        T=read();
        while(T--)
        {
            cnt=0;
            n=read();q=read();
            for(int i=1;i<=q;i++)
            {
                s[i][0]=read();c[i]=read();
                for(int j=1;j<=s[i][0];j++)
                    s[i][j]=read();//读入套餐
            }
            for(int i=1;i<=n;i++)
                city[i].x=read(),city[i].y=read();
            for(int i=1;i<=n;i++)
            {
                for(int j=i+1;j<=n;j++)
                {
                    edge[++cnt].u=i;
                    edge[cnt].v=j;
                    edge[cnt].w=(city[i].x-city[j].x)*(city[i].x-city[j].x)+(city[i].y-city[j].y)*(city[i].y-city[j].y);
                }
            }
            sort(edge+1,edge+1+cnt,cmp);
            ans=init_kruskal();//原始图的最小生成树
            //cout<<ans<<endl;
            for(int i=1;i<n;i++)
            {
                g[i].u=edge[save[i]].u;
                g[i].v=edge[save[i]].v;
                g[i].w=edge[save[i]].w;
            }//建一个新图
            /*for(int i=1;i<n;i++)
            {
                cout<<g[i].u<<" "<<g[i].v<<" "<<g[i].w<<endl;
            }
            cout<<endl;*/
            solve();//准备枚举
            cout<<ans<<endl;
            if(T)cout<<endl;//UVA不会省略最后的换行符
        }
        return 0;
    }
    
  • 相关阅读:
    Arduino-原理图标识
    python-垃圾回收机制
    利用浮力测密度
    sys模块-与python解释器交互的模块
    第十一章第二节 功率
    第十一章第一节 功
    类-描述器-把类对象方法转变为属性方式
    H5浏览器播放RTMP直播流
    如何查看某个端口被谁占用
    OBS第三方推流直播教程
  • 原文地址:https://www.cnblogs.com/hulean/p/11123030.html
Copyright © 2020-2023  润新知