存在免费套餐的最小生成树 Uva1151

题意:
求最小生成树。( n 2 n^2 n2条边:需要自己算一共有 n n n个点的坐标)
不过你可以选择一个套餐,花费 c c c使得套餐内点全部连通。
套餐数量 q ≤ 8 , n ≤ 1000 q≤8,n≤1000 q8n1000
题解:
套餐数量较少,我们可以枚举(二进制即可)。
枚举之后把所有包含的边权赋值为 0 0 0
求最小生成树并且能够使得(边权赋值为 0 0 0的), p r i m prim prim不太好实现。
思路一:
枚举 ∗ ( *( (排序新边+ k r u s k a l ) kruskal) kruskal)
显然复杂度为 2 8 ∗ ( 8 ∗ n + 2 n 2 l o g 2 n + n 2 ) ≈ 1 0 8 2^8*(8*n+2n^2log_2n+n^2)≈10^8 28(8n+2n2log2n+n2)108朝上
思路二:
我们考虑免费套餐之后,其实我们只需要判连通即可,对枚举选中的免费套餐里的点连通,
再跑最小生成树,当所有点连通就退出。
但是这样的复杂度也是 2 8 ∗ ( 8 ∗ n + n 2 + n l o g n ) + 2 n 2 l o g n 2^8*(8*n+n^2+nlogn)+2n^2logn 28(8n+n2+nlogn)+2n2logn,常数小了很多但是还是不行。
主要在于每次求最小生成树用了 n 2 n^2 n2条边(最多,但是如果有一个点特别远,就会卡到 n 2 n^2 n2)
思路三:
我们考虑先做出一个最小生成树,对于上面的边能够使得所有点连通,我们每次对套餐内的点连通后,至多处理 n − 1 n-1 n1条边(如果这条边无法影响就不加上价值)。再往后遍历也是没有意义的。(因为如果较大的边连接的点在套餐里,那么已经被选择过了,如果不在:那为什么不选前面的边呢。
复杂度: 2 8 ( 8 ∗ n + n − 1 ) = 1 e 6 2^8(8*n+n-1)=1e^6 28(8n+n1)=1e6,因为 2 ∗ 9 = 18 2*9=18 29=18

#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<map>
#include<vector>
#include<set>
#include<queue>
#include<cctype>
#include<string>
#include<cmath>
#define FOR(i,a,b) for(int i=a;i<=b;i++)
using namespace std;

#define ll long long
#define inf 0x3f3f3f3f
#define eps 1e-6

const int maxn = 10010;

int n,q,cnt=0;
int f[1010];ll value[10];
ll d[1010][1010];
pair<ll,ll>pos[1010];
vector<int>G[10],tree;

int find(int x){return f[x]==x?x:(f[x]=find(f[x]));}
void uni(int x,int y){f[find(x)]=find(y);}
bool query(int x,int y){return find(x)==find(y);}

struct node{
    int x,y;ll dist;
    friend bool operator < (node a,node b){
        return a.dist<b.dist;
    }
}A[1000010];

void init(){
    for(int i=1;i<n;i++){
        for(int j=i+1;j<=n;j++){
            d[j][i]=d[i][j]=(pos[i].first-pos[j].first)*(pos[i].first-pos[j].first)+(pos[i].second-pos[j].second)*(pos[i].second-pos[j].second);
        }
    }
    cnt=0;
    for(int i=1;i<n;i++){
        for(int j=i+1;j<=n;j++){
            A[++cnt]=node{i,j,d[i][j]};
        }
    }
    sort(A+1,A+1+cnt);
    tree.clear();
    for(int i=1;i<=n;i++)f[i]=i;
    int now=n;
    for(int i=1;i<=cnt;i++){
        if(!query(A[i].x,A[i].y)){
            tree.push_back(i);
            uni(A[i].x,A[i].y);
            now--;
            if(now==1)break;
        }
    }
}

int main(){
    int T;scanf("%d",&T);
    for(int kase=1;kase<=T;kase++){
        scanf("%d%d",&n,&q);
        FOR(i,1,q){
            int num;scanf("%d%lld",&num,&value[i]);
            G[i].clear();
            FOR(j,1,num){
                int x;scanf("%d",&x);
                G[i].push_back(x);
            }
        }
        FOR(i,1,n){
            scanf("%lld%lld",&pos[i].first,&pos[i].second);
        }
        init();
        ll ans=0x3f3f3f3f3f3f3f3f;
        for(int i=0;i<(1<<q);i++){
            for(int j=1;j<=n;j++)f[j]=j;
            ll res=0;
            for(int j=1;j<=q;j++){
                int x=(1<<(j-1));
                if(i&x){
                    res+=value[j];
                    for(int k=0;k<G[j].size()-1;k++){
                        uni(G[j][k],G[j][k+1]);
                    }
                }
            }
            for(int j=0;j<tree.size();j++){
                int u=tree[j];
                if(!query(A[u].x,A[u].y)){
                    uni(A[u].x,A[u].y);
                    res+=A[u].dist;
                }
            }
            ans=min(res,ans);
        }
        cout<<ans<<endl;if(kase<T)puts("");
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值