zoukankan      html  css  js  c++  java
  • 部落划分 (二分+并查集/kruskal)

    问题:

    聪聪研究发现,荒岛野人总是过着群居的生活,但是,并不是整个荒岛上的所有野人都属于同一个部落,野人们总是拉帮结派形成属于自己的部落,不同的部落之间则经常发生争斗。只是,这一切都成为谜团了——聪聪根本就不知道部落究竟是如何分布的。
    不过好消息是,聪聪得到了一份荒岛的地图。地图上标注了N个野人居住的地点(可以看作是平面上的坐标)。我们知道,同一个部落的野人总是生活在附近。我们把两个部落的距离,定义为部落中距离最近的那两个居住点的距离。聪聪还获得了一个有意义的信息——这些野人总共被分为了K个部落!这真是个好消息。
    聪聪希望从这些信息里挖掘出所有部落的详细信息。他正在尝试这样一种算法:
    对于任意一种部落划分的方法,都能够求出两个部落之间的距离,聪聪希望求出一种部落划分的方法,使靠得最近的两个部落尽可能远离。 例如,下面的左图表示了一个好的划分,而右图则不是。请你编程帮助聪聪解决这个难题。

    解:

    再次明确几点重要的东西 别搞忘了

    1:处理集合问题一定要用并查集

    2: 最近的最远 最远的最近 不是二分就是生成树

    考试的时候想到正解的 突然卡住 难受QWQ

    $1$

    二分一个最小距离

    暴力枚举节点之间的距离,小于枚举的答案就合并成一个集合 

                        两个部落的距离只会比这个更小所以必须合并

    大于的话是另一个集合(考试的时候我就是在这里卡住了 我还以为他会被别的点更新 但实际上是不会的 因为暴力枚举的是所有点的距离)

    并查集套用即可

    //
    #include<bits/stdc++.h>
    using namespace std;
    #define maxnn 100000
    #define ll long long 
    ll n,k;
    ll f[maxnn];
    ll x[maxnn],y[maxnn];
    double dist(ll x1,ll x2,ll y1,ll y2)
    {
        return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
    }
    int gf(int v)
    {
        if(f[v]==v) return v;
        else return f[v]=gf(f[v]);
    }
    void merge(int v,int u)
    {
        int f1=gf(v);
        int f2=gf(u);
        if(f1!=f2) f[f1]=f2;
    }
    bool isok(double ttt)
    {
        for(int i=1;i<=n;i++)
        {
            f[i]=i;
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                if(i!=j)
                {
                    double dis=dist(x[i],x[j],y[i],y[j]);
                    if(dis<ttt) merge(i,j);
                }
            }
            
        }
        int sec=0;
            for(int i=1;i<=n;i++)
        {
            if(f[i]==i) sec++;
        }
        return sec>=k;
    }
    int main()
    {
        
        cin>>n>>k;
        double  l=0,r=12000,eps=1e-1;
        for(int i=1;i<=n;i++)
        {
            scanf("%lld%lld",&x[i],&y[i]);
        }
        int cnt=0;
        while(cnt<=60) 
        {
            cnt++;
            double mid=(l+r)/2;
            if(isok(mid)) l=mid;
            else r=mid;
        }
        printf("%.2lf",l);
    }

    $2$ kruskal 

    注意到最大距离最小  不只可以二分 还可以跑生成树 

    生成树也是处理集合问题的利器

    按照边从小到大排序 

    优先合并小的 直到联通块(集合)的值刚好 ==k;

    输出下一条边

    来自hyhjulao 的code:

    #include<bits/stdc++.h>
    using namespace std;
    template<typename T>
    void rin(T &t)
    {t=0;int k=1;char c=getchar();while(!isdigit(c)){if(c=='-')k=-1;c=getchar();}while(isdigit(c)){t=t*10+c-'0';c=getchar();}t*=k;}
    const int maxn=1e3+5;
    struct node
    {
        int a,b;
        double c;
    }e[maxn*maxn];
    int n,k,fa[maxn],tot;
    bool cmp(node x,node y)
    {return x.c<y.c;}
    typedef pair<int,int> P;
    P NODE[maxn];
    inline double dis(int i,int j)
    {return sqrt((NODE[i].first-NODE[j].first)*(NODE[i].first-NODE[j].first)+(NODE[i].second-NODE[j].second)*(NODE[i].second-NODE[j].second));}
    int getfa(int v)
    {return fa[v]==v?v:fa[v]=getfa(fa[v]);}
    int main()
    {
        rin(n);rin(k);
        for(int i=1;i<=n;i++)
        {
            rin(NODE[i].first);rin(NODE[i].second);
            fa[i]=i;
        }
        for(int i=1;i<=n;i++)
        for(int j=1;j<i;j++)
        {
            e[++tot].a=i;e[tot].b=j;
            e[tot].c=dis(i,j);
        }
        sort(e+1,e+1+tot,cmp);
        int cnt=n,fx,fy;
        double ans;
        for(int i=1;i<=tot;i++)
        {
            fx=getfa(e[i].a);
            fy=getfa(e[i].b);
            if(fx!=fy)
            {
                if(cnt==k){ans=e[i].c;break;}
                else cnt--;
                fa[fx]=fy;
            }
        }
        printf("%.2lf",ans);
    
    }
    刀剑映出了战士的心。而我的心,漆黑且残破
  • 相关阅读:
    long和Long的区别
    C语言的变量的内存分配
    Java蓝桥杯 算法提高 九宫格
    Java实现 蓝桥杯算法提高金明的预算方案
    Java实现 蓝桥杯 算法提高 新建Microsoft world文档
    Java实现 蓝桥杯 算法提高 快乐司机
    Java实现 蓝桥杯 算法提高 三角形
    Java实现 蓝桥杯 算法提高 三角形
    Java实现 蓝桥杯 算法提高 三角形
    Java实现 蓝桥杯 算法提高 三角形
  • 原文地址:https://www.cnblogs.com/OIEREDSION/p/11370731.html
Copyright © 2011-2022 走看看