D135 最小生成树 Prim 算法 P5425 [USACO19OPEN] I Would Walk 500 Miles G

D135 最小生成树 Prim 算法 P5425 [USACO19OPEN] I Would Walk 500 Miles G_哔哩哔哩_bilibili

 

P5425 [USACO19OPEN] I Would Walk 500 Miles G - 洛谷

N 个点分成 K 组,任意两组之间的最小边权最大化,输出这个最大化的边权。

思路

按题意连边,两点($i<j$)之间的边权为 $(2019201913*i+2019201949*j)\% 2019201997$

连边后发现是个完全图,完全图和稠密图都会卡 Kruskal 算法,$O(2.8\times 10^7\times log(2.8\times 10^7))$

用 $O(7500^2)$ 的 Prim 算法,求出最小生成树的边权集合 $q$,排个序

先把 $n$ 个点划分为 $n$ 组,贪心地从小到大添加最小生成树上的边,合并组,每添加一条边就会减少一组

添加 $n-k$ 条边,组的数量就会变成 $k$ 组,所以第 $n-k+1$ 条边的边权就是 $k$ 组之间的最小边权,即答案

图中实线为最小生成树的边,虚线为非树边,绿圈为分组

相关板子:

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

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

// 最小生成树 Prim算法 O(n^2)
#include<bits/stdc++.h>
using namespace std;

const int N=7505,M=2019201997;
int n,k,cnt;
vector<pair<int,int>> e[N];
int d[N],vis[N],q[N];

void prim(){
  for(int i=0;i<=n;i++) d[i]=M; d[1]=0;
  for(int i=1,u;i<=n;i++){
    u=0;
    for(int j=1;j<=n;j++)if(!vis[j]&&d[j]<d[u]) u=j;
    vis[u]=1;
    if(d[u]) q[++cnt]=d[u]; //记录边权
    for(auto [v,w]:e[u])if(d[v]>w) d[v]=w;
  }
  
  sort(q+1,q+cnt+1);
  printf("%d\n",q[n-k+1]);
}
int main(){
  cin>>n>>k;
  for(int i=1,w;i<n;i++)for(int j=i+1;j<=n;j++){
    w=(1ll*2019201913*i%M+1ll*2019201949*j%M)%M;
    e[i].push_back({j,w});
    e[j].push_back({i,w});
  }
  
  prim();
}

 

// 最小生成树 Kruskal算法 O(MlogM) TLE两点
#include<bits/stdc++.h>
using namespace std;

const int N=7505,M=N*N/2,P=2019201997;
int n,k,m,tot,ans,fa[N];
pair<int,pair<int,int> >e[M]; //边集

int find(int u){ //并查集的找根
  return fa[u]==u?u:fa[u]=find(fa[u]);
}
void kruskal(){
  sort(e+1,e+m+1); //排序
  for(int i=1; i<=n; i++) fa[i]=i;
  for(int i=1; i<=m; i++){
    int x=find(e[i].second.first),y=find(e[i].second.second);
    if(x!=y){
      fa[x]=y;
      ans=e[i].first;
      if(++tot==n-k+1) break;
    }
  }
  cout<<ans;
}
signed main(){
  cin>>n>>k;
  for(int i=1;i<n;i++)for(int j=i+1;j<=n;j++){
    e[++m]={(1ll*2019201913*i%P+1ll*2019201949*j%P)%P,{i,j}};
  }
    
  kruskal();
}

 

posted @ 2026-04-16 11:07  董晓  阅读(84)  评论(0)    收藏  举报