déce. 28 两道题

https://www.luogu.com.cn/problem/P2820

菜题一天做一道还是太浪费时间了

最小生成树,输入数据保证边权为正
但是原始图可能不连通,生成树要保证图的不连通性
问题不大,建立"超级父亲"就行了(假,见注)

不对,求最小生成树不会加原本没有的边,所以不会更改原图的连通性
所以最终选上的边数肯定会小于\(n-1\)
但是不能用\(n-[连通块个数]\)来决定选边终止的时候(假)
可以用,还是连通性与并查集的问题:一个块一旦连通,这个块就不能再加边了

不用\(n-[连通块个数]\)决定选边的代码

#include<bits/stdc++.h>
using namespace std;
#define in Read()
typedef long long ll;

int in{
    int i=0,f=1; char ch=0;
    while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if(ch=='-') f=-1, ch=getchar();
    while('0'<=ch&&ch<='9') i=(i<<1)+(i<<3)+ch-48, ch=getchar();
    return i*f;
}

const int N=1e4+5;
int n,m,cnt,fa[N],supfa,tot,revans;
struct edge{
    int u,v,w;
    edge(){};
    edge(int U,int V,int W){u=U,v=V,w=W;}
    friend bool operator < (const edge &a, const edge &b){return a.w<b.w;}
}e[N];

inline int get(int x){ return fa[x]==x?x:fa[x]=get(fa[x]);}

int main(){

    // freopen("1.in","r",stdin);

    n=in,m=in; supfa=n;
    for(int i=1;i<=n;++i) fa[i]=i;
    for(int i=1;i<=m;++i){
        int u=in,v=in,w=in;
        e[i]=edge(u,v,w);
        tot+=w;
    }

    sort(e+1,e+m+1);

    for(int i=1;i<=m;++i){
        int u=get(e[i].u), v=get(e[i].v);
        if(u==v) continue;
        fa[u]=v;
        revans+=e[i].w;
    }

    printf("%d\n",tot-revans);
    return 0;
    
}

利用\(n-[连通块个数]\)优化的代码

#include<bits/stdc++.h>
using namespace std;
#define in Read()
typedef long long ll;

int in{
    int i=0,f=1; char ch=0;
    while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if(ch=='-') f=-1, ch=getchar();
    while('0'<=ch&&ch<='9') i=(i<<1)+(i<<3)+ch-48, ch=getchar();
    return i*f;
}

const int N=1e4+5;
int n,m,cnt,fa[N],supfa,tot,revans,block;
struct edge{
    int u,v,w;
    edge(){};
    edge(int U,int V,int W){u=U,v=V,w=W;}
    friend bool operator < (const edge &a, const edge &b){return a.w<b.w;}
}e[N];

inline int get(int x){ return fa[x]==x?x:fa[x]=get(fa[x]);}

int main(){

    // freopen("1.in","r",stdin);

    n=in,m=in; supfa=n;
    for(int i=1;i<=n;++i) fa[i]=i;
    for(int i=1;i<=m;++i){
        int u=in,v=in,w=in;
        e[i]=edge(u,v,w);
        tot+=w;
        u=get(u),v=get(v);
    }

    sort(e+1,e+m+1);
    sort(fa+1,fa+n+1);
    for(int i=1;i<=n;++i) if(fa[i]^fa[i-1]) ++block;
    for(int i=1;i<=n;++i) fa[i]=i;

    for(int i=1;i<=m;++i){
        int u=get(e[i].u), v=get(e[i].v);
        if(u==v) continue;
        ++cnt;
        fa[u]=v;
        revans+=e[i].w;
        if(cnt==n-block) break;
    }

    printf("%d\n",tot-revans);
    return 0;
    
}

注:上面所说的超级父亲,只是一种预处理所有连通块,然后分块求最小生成树的办法,不太好写
预处理所有连通块,然后让连通块中的节点都指向一个”超级父亲“(序号大于\(n\)

https://www.luogu.com.cn/problem/P1991

第一眼:老二分答案了
第二眼:似乎不需要二分答案,按边权从大到小选装卫星电话的哨所就行了
第三眼:还是要二分答案啊,对于每个答案,求连通块个数,连通块个数\(<S\)的合法

但这雀食不优,而且WA3个点qwq
剩下的明天de,今天肝不动了(该死的新冠qwq)

de出来了,原先用并查集计算连通块个数的方法错了
利用每次合并操作减少一个连通块得到最终的连通块个数

#include<bits/stdc++.h>
using namespace std;
#define in Read()
typedef long long ll;

int in{
    int i=0,f=1; char ch=0;
    while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if(ch=='-') f=-1, ch=getchar();
    while('0'<=ch&&ch<='9') i=(i<<1)+(i<<3)+ch-48, ch=getchar();
    return i*f;
}

const int N=600;
const double eps=1e-4;
int s,p,cnt,fa[N],block;
struct node{
    double x,y;
}d[N];
struct edge{
    int u,v;
    double w;
    edge(){};
    edge(int uu,int vv,double ww){ u=uu, v=vv, w=ww;}
    friend bool operator < (const edge &a, const edge &b){ return a.w<b.w;}
}e[N*N];
double l,r=2e8;

double Dist(const node &a, const node &b){ return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));}
int get(int x){ return fa[x]==x?x:fa[x]=get(fa[x]);}

bool check(double mid){
    for(int i=1;i<=p;++i) fa[i]=i; block=p;
    for(int i=1;i<=cnt;++i){
        if(e[i].w>mid+eps) break;
        int u=get(e[i].u), v=get(e[i].v);
        if(u==v) continue;
        fa[u]=v; --block;
    }
    // printf("%d %lf\n",block,mid);
    return block<=s;
}

int main(){ 

    // freopen("1.in","r",stdin);

    s=in, p=in;
    for(int i=1;i<=p;++i) d[i].x=in, d[i].y=in;
    for(int i=1;i<=p;++i)
        for(int j=i+1;j<=p;++j)
            e[++cnt]=edge(i,j,Dist(d[i],d[j]));
    
    sort(e+1,e+cnt+1);

    while(r-l>eps){
        double mid=(l+r)/2;
        if(check(mid)) r=mid;
        else l=mid;
    }

    printf("%.2lf\n",r);
    return 0;
    
}

当然还有更好的方法:
从小到大加边,会遇到两种情况

  1. 再加入一条边使连通块个数从\(s+1\)变成\(s\),设此边长\(d_1\)
  2. 再加入一条边使连通块个数从\(s\)变成\(s-1\),设此边长\(d_2\)

\(d_1, d_2\)之间的边长都合法
要求最短边,那就取\(d_1\)

然后这题就随手切了

#include<bits/stdc++.h>
using namespace std;
#define in Read()
typedef long long ll;

int in{
    int i=0,f=1; char ch=0;
    while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if(ch=='-') f=-1, ch=getchar();
    while('0'<=ch&&ch<='9') i=(i<<1)+(i<<3)+ch-48, ch=getchar();
    return i*f;
}

const int N=600;
int s,p,fa[N],block,cnt;
struct node{ int x,y;}d[N];

struct edge{
    int u,v;
    double dis;
    edge(){};
    edge(int uu,int vv,double ddis){ u=uu, v=vv, dis=ddis;}
    friend bool operator < (const edge &a, const edge &b){ return a.dis<b.dis;}
}e[N*N];

double dis(int u,int v){ return sqrt((d[u].x-d[v].x)*(d[u].x-d[v].x)+(d[u].y-d[v].y)*(d[u].y-d[v].y));}
int get(int x){return fa[x]==x?x:fa[x]=get(fa[x]);}

int main(){

    // freopen("1.in","r",stdin);

    s=in, p=in;
    for(int i=1;i<=p;++i) fa[i]=i; block=p;
    for(int i=1;i<=p;++i)
        d[i].x=in, d[i].y=in;
    
    for(int i=1;i<=p;++i)
        for(int j=i+1;j<=p;++j)
            e[++cnt]=edge(i,j,dis(i,j));
    
    sort(e+1,e+cnt+1);

    for(int i=1;i<=cnt;++i){
        int u=get(e[i].u), v=get(e[i].v);
        if(u==v) continue;
        fa[u]=v;
        --block;
        if(block==s){ printf("%.2lf\n",e[i].dis); return 0;}
    }
    
}
posted @ 2022-12-28 18:43  antimo  阅读(12)  评论(0编辑  收藏  举报