D136 最小生成树 Kruskal 算法 P8191 [USACO22FEB] Moo Network G

D136 最小生成树 Kruskal 算法 P8191 [USACO22FEB] Moo Network G_哔哩哔哩_bilibili

 

P8191 [USACO22FEB] Moo Network G - 洛谷         P2872 [USACO07DEC] Building Roads S - 洛谷

给定 n 个点的坐标,求连通 n 个点的最低成本。

思路

两题的思路都是求最小生成树,但是数据范围不同

P2872:$n\le 1000,1\le x,y\le 10^6$,$n$ 个点直接双重循环建图

P8191:$n\le 100000,0\le x\le 10^6,0\le y\le 10$,$n$ 个点不能双重循环建图

坐标点都取整数且分布在一个狭长的区带,每个点只需要连 “附近” 的点,先对 $n$ 个点按 $x$ 坐标排序

这个 “附近”  究竟是多少呢?

考虑最坏情况,$\frac{1}{4}$ 圆内仅圆心 $O(0,10)$ 一个点,圆弧外的格点数用程序求出为 $31$ 个,再取圆弧上的一点 $A(10,10)$

弧外的 $31$ 个点的排序均介于 $O$ 点与 $A$ 点之间,$O$ 点需要先连上 $31$ 个点,再连上 $A$ 点,$OA$ 就是一条最小生成树的边

所以,每个点向后连至少 $32$ 个点,洛谷上的题解都没有论证这个下界,连接点数少于 $32$,就会被下面构造的数据 $Hack$ 掉

相关板子:

D08【模板】最小生成树 Kruskal 算法 - 董晓 - 博客园

圆内格点数可参考: 高斯圆问题-数学百科

 

// Kruskal算法 O(mlogm)
#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;

const int N=1e5+5,M=3.2e6+5;
int n,m,ans,tot,fa[N];
struct A{int x,y;} a[N]; //坐标
struct E{int x,y,d;} e[M]; //边集

int find(int u){ //并查集的找根
  return fa[u]==u?u:fa[u]=find(fa[u]);
}
void kruskal(){
  for(int i=1;i<=n;i++) fa[i]=i;
  sort(e+1,e+1+m,[&](E a,E b){return a.d<b.d;});
  for(int i=1;i<=m;i++){
    int x=find(e[i].x),y=find(e[i].y);
    if(x!=y){
      fa[x]=y;
      ans+=e[i].d;
      if(++tot==n-1) break;
    }
  }
  printf("%lld\n",ans);
}
signed main(){
  scanf("%lld",&n);
  for(int i=1,x,y;i<=n;i++){
    scanf("%lld%lld",&x,&y); a[i]={x,y};
  }
  sort(a+1,a+n+1,[&](A a,A b){
    if(a.x!=b.x) return a.x<b.x;
    return a.y<b.y;
  });
  for(int i=1;i<=n-1;i++)for(int j=i+1; j<=min(i+32,n); j++){
    e[++m]={i,j,(a[i].x-a[j].x)*(a[i].x-a[j].x)+(a[i].y-a[j].y)*(a[i].y-a[j].y)};
  }
    
  kruskal();
}

 

// 构造 Hack 数据
#include<bits/stdc++.h>
using namespace std;

int main(){
  int s=0;
  for(int x=0;x<=10;x++)
    for(int y=0;y<=10;y++)
      if(x*x+(10-y)*(10-y)>10*10) s++;
  printf("%d\n",s+2); //弧外31个点+两个点
  
  printf("%d %d\n",0,10); //圆心
  for(int x=0;x<=10;x++)
    for(int y=0;y<=10;y++)
      if(x*x+(10-y)*(10-y)>10*10) printf("%d %d\n",x,y);

  printf("%d %d\n",10,10); //补个弧上点
}

Hack 数据,正确答案 131

33
0 10
1 0
2 0
3 0
4 0
5 0
5 1
6 0
6 1
7 0
7 1
7 2
8 0
8 1
8 2
8 3
9 0
9 1
9 2
9 3
9 4
9 5
10 0
10 1
10 2
10 3
10 4
10 5
10 6
10 7
10 8
10 9
10 10

 

posted @ 2026-04-17 11:24  董晓  阅读(125)  评论(0)    收藏  举报