洛谷 題解 UVA1151 【買仍是建 Buy or Build】

【題意】

平面上有\(n(n<=1000)\)個點,你的任務是讓全部n個點聯通。爲此,你能夠新建一些邊,費用等於兩個端點的歐幾里得距離平方。另外還有\(q(q<=8)\)個套餐能夠購買,若是你購買了第\(i\)個套餐,該套餐中的全部結點將變得相互鏈接。第\(i\)個套餐的花費爲\(C_i\)c++

【算法】

\(Kruskal\)算法

【分析】

最容易想到的算法是:先枚舉購買哪些套餐,把套餐中包含的權值設爲\(0\),而後求最小生成樹。因爲枚舉量爲\(O(2^q)\),給邊排序的時間複雜度爲\(O(n^2logn)\),而排序以後每次\(kruskal\)算法的時間複雜度爲\(O(n^2)\),所以總時間複雜度爲\(O(2^qn^2+n^2logn)\),對於題目來講的規模太大了。優化

只需一個小小的優化便可下降時間複雜度:先求一次原圖 (不購買任何套餐) 的最小生成樹,獲得\(n-1\)條邊,其他的邊就沒用了。而後枚舉買哪些套餐(這裏能夠用狀態壓縮的思想),則枚舉套餐後再求最小生成樹時,圖上的邊已經寥寥無幾。spa

爲何能夠這樣呢? 大部分題解都沒有證實。這裏給出證實過程code

首先回顧一下,在\(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;
}
相關文章
相關標籤/搜索