主要参考lg题单,未来会改会删。
1 lgP2975
开始看数据不大想用prim,想了想还是算了,万一是断断续续的那种不连续的边找已经有了的边对应的顶点不好找。开始被骗了,疯狂re还wa,以为边最多1e3后来仔细读了题看到是“已有的边”,想了想可能是稠密图,且主要思想是把已有了的m条边赋值为0,赋值为0的边最小肯定先算入最小树了,不必像prim一样担心诸如“最小树中的边不包含已有的边该怎么解决”,kruskal很方便。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <bits/stdc++.h>
using namespace std;
const int N=1010,M=2e6+10;//点虽不多但边可能很多,边开小了会re
int cnt;//记录边数
double res;
int p[N],n,m;
struct bian
{
int x,y;
double z;
}s[M];
struct node
{
int x,y;
}dian[N];
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
bool cmp(bian a,bian b)
{
if(a.z==b.z)
return a.x<b.x;
return a.z<b.z;
}
void kruskal()
{
int top=0;
sort(s+1,s+1+cnt,cmp);
for(int i=1;i<=cnt;i++)
{
if(find(s[i].x)!=find(s[i].y))
{
p[find(s[i].x)]=find(s[i].y);
res+=s[i].z;
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
p[i]=i;
for(int i=1;i<=n;i++)
{
cin>>dian[i].x>>dian[i].y;
}
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
cnt++;
s[cnt].x=i;
s[cnt].y=j;
s[cnt].z=(double)sqrt((double)(dian[i].x-dian[j].x)*(dian[i].x-dian[j].x)+(double)(dian[i].y-dian[j].y)*(dian[i].y-dian[j].y));
//强转一波不然会错
}
}
for(int i=1;i<=m;i++)//之前已有的路再建花费为0
{
int x,y;
cin>>x>>y;
cnt++;
s[cnt].x=x;
s[cnt].y=y;
s[cnt].z=0.0;//0.0
}
kruskal();
printf("%.2lf",res);
return 0;
}
2 lgP2121
大水题照着样例想了半天,怪我对kruskal理解不够深刻,被树限制住了,其中对边排序后并查集并非只针对单独一个连通图,连通分量间也可以。这就是一个不一定所有点都连通的、最多k条边的、"最大森林”的边权和。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1e5+10,M=2e5+10;
int p[N],n,m,res,cnt,k;
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
struct bian
{
int x,y,z;
}s[M];
bool cmp(bian a,bian b)
{
return a.z>b.z;
}
void kruskal()
{
sort(s+1,s+1+m,cmp);
for(int i=1;i<=m;i++)
{
int x=s[i].x,y=s[i].y,z=s[i].z;
if(find(y)!=find(x))
{
p[find(x)]=find(y);
res+=z;
cnt++;
if(cnt==k)
{
cout<<res;
return;
}
}
}
return;
}
int main()
{
cin>>n>>m>>k;
for(int i=1;i<=n;i++)
p[i]=i;
for(int i=1;i<=m;i++)
{
int x,y,z;
cin>>x>>y>>z;
s[i].x=x,s[i].y=y,s[i].z=z;
}
kruskal();
return 0;
}
3 lgP1396
怎么保证最大的最小?边排序后从小到大加边,并查集判断当s和t间第一次互通(即s和t间刚形成最小树时)最大边一定最小。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1e4+10,M=2e4+10;
int p[N],n,m,ss,tt,res,cnt;
struct bian
{
int a,b,c;
}s[M];
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
bool cmp(bian a,bian b)
{
return a.c<b.c;
}
void kruskal()
{
sort(s+1,s+1+m,cmp);
for(int i=1;i<=m;i++)
{
int a=s[i].a,b=s[i].b,c=s[i].c;
if(find(a)!=find(b))
{
p[find(a)]=find(b);
res=max(res,c);
cnt++;
if(cnt==n-1 || find(ss)==find(tt))
{
cout<<res;
return;
}
}
}
}
int main()
{
cin>>n>>m>>ss>>tt;
for(int i=1;i<=n;i++)
p[i]=i;
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
s[i].a=a,s[i].b=b,s[i].c=c;
}
kruskal();
return 0;
}
4 lgP1195
起初想法是只需要k个点就行,找k个点的最小树的权值和。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1e3+10,M=1e4+10;
int p[N],n,m,k,res,cnt,ans;
bool st[N];
struct bian
{
int a,b,c;
}s[M];
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
bool cmp(bian a,bian b)
{
return a.c<b.c;
}
void kruskal()
{
sort(s+1,s+m+1,cmp);
for(int i=1;i<=m;i++)
{
int a=s[i].a,b=s[i].b,c=s[i].c;
if(find(a)!=find(b))
{
p[find(a)]=find(b);
res+=c;
cnt++;
if(cnt==n-1 && ans<k)
{
cout<<"No Answer";
return;
}
if(st[a]==false)
{
st[a]=true;
ans++;
}
if(st[b]==false)
{
st[b]=true;
ans++;
}
if(ans==k)
{
cout<<res;
return;
}
}
}
}
int main()
{
cin>>n>>m>>k;
if(k>n)
{
cout<<"No Answer";
}
for(int i=1;i<=n;i++)
p[i]=i;
for(int i=1;i<=n;i++)
{
int a,b,c;
cin>>a>>b>>c;
s[i].a=a,s[i].b=b,s[i].c=c;
}
kruskal();
return 0;
}
交了一发全wa发现好像理解错了,原来棉花糖不是连线的才算一个,图中所有的都算,而一个树只对应一个棉花糖。。。所以完全想反了,反而是只需要连n-k条最小边,自然就有k个连通分量(棉花糖)了。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1e3+10,M=1e5+10;
int p[N],n,m,k,res,cnt;
struct bian
{
int a,b,c;
}s[M];
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
bool cmp(bian a,bian b)
{
return a.c<b.c;
}
void kruskal()
{
sort(s+1,s+1+m,cmp);
for(int i=1;i<=m;i++)
{
int a=s[i].a,b=s[i].b,c=s[i].c;
if(find(a)!=find(b))
{
p[find(a)]=find(b);
res+=c;
cnt++;
if(cnt==n-k)
{
cout<<res;
return;
}
}
}
cout<<"No Answer";
}
int main()
{
cin>>n>>m>>k;
if(n-k>m)
{
cout<<"No Answer";
return 0;
}
for(int i=1;i<=n;i++)
p[i]=i;
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
s[i].a=a,s[i].b=b,s[i].c=c;
}
kruskal();
return 0;
}
5 lgP1194
开始又在纠结不连通可能导致多个起点,后来一想太水了,有多个连通分量也可以化成一个起点,办法是除了第一个起点,买其它点的花费都记在与前一个点连着的边上,所以即使不连通的分量间也可以画出一条默认花费的边使其连通,因此连出的最小树就是最小的花费。将这些边从小到大比较至于起点的花费单独加一下一个点的默认花费。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1100,M=1e6+10;
int p[N],n,m,res,cnt,ans;
struct bian
{
int a,b,c;
}s[M];
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
bool cmp(bian a,bian b)
{
return a.c<b.c;
}
void kruskal()
{
sort(s+1,s+1+ans,cmp);
for(int i=1;i<=ans;i++)
{
int a=s[i].a,b=s[i].b,c=s[i].c;
if(find(a)!=find(b))
{
p[find(a)]=find(b);
res+=c;
cnt++;
if(cnt>=m)
return;
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
p[i]=i;
for(int i=1;i<=m;i++)
{
for(int j=1;j<=m;j++)
{
int x;
cin>>x;
if(i!=j)
{
ans++;
s[ans].a=i;
s[ans].b=j;
if(x==0)
{
s[ans].c=n;
continue;
}
s[ans].c=min(n,x);//优惠反而更贵换成默认值
}
}
}
kruskal();
cout<<res+n;
return 0;
}
6 lgP1991
间接相连也算,找最短路边从小到大排序,注意n>=m特判,找需要用无线电的m-n条小边中最大的即为D。
#include <bits/stdc++.h>
using namespace std;
const int N=1010,M=1e4+10;
int p[N],n,m,cnt,ans;
double res=0.0;
struct node
{
int x,y;
}d[N];
struct bian
{
int x,y;
double z;
}s[M];
double dis(node a,node b)
{
return (double)sqrt(pow((double)(a.x-b.x),2)+pow((double)(a.y-b.y),2));
}
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
bool cmp(bian a,bian b)
{
return a.z<b.z;
}
void kruskal()
{
sort(s+1,s+1+ans,cmp);
for(int i=1;i<=ans;i++)
{
int a=s[i].x,b=s[i].y;
double c=s[i].z;
if(find(a)!=find(b))
{
p[find(a)]=find(b);
res=max(res,c);
cnt++;
if(cnt==m-n)
return;
}
}
}
int main()
{
cin>>n>>m;
if(n>=m)
{
cout<<0;
return 0;
}
for(int i=1;i<=m;i++)
p[i]=i;
for(int i=1;i<=m;i++)
{
cin>>d[i].x>>d[i].y;
}
for(int i=1;i<=m;i++)
{
for(int j=1;j<=m;j++)//无向边
{
if(i!=j)
{
ans++;
s[ans].x=i;
s[ans].y=j;
s[ans].z=dis(d[i],d[j]);
}
}
}
kruskal();
printf("%.2lf",res);
return 0;
}
7 lgP2700
基本同上,离着近的肯定划分为一个部落,最小树上最远的k-1条边一定连着k个部落,所以找到最小树中第n-k+1小的边就行。
#include <bits/stdc++.h>
using namespace std;
const int N=2e3+10,M=4e6+10;
int p[N],n,k,cnt,ans;
double res;
struct bian
{
int a,b;
double c;
}s[M];
struct node
{
int x,y;
}d[N];
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
bool cmp(bian a,bian b)
{
return a.c<b.c;
}
double dis(node a,node b)
{
return (double)sqrt(pow((double)(a.x-b.x),2)+pow((double)(a.y-b.y),2));
}
void kruskal()
{
sort(s+1,s+1+ans,cmp);
for(int i=1;i<=ans;i++)
{
int a=s[i].a,b=s[i].b;
double c=s[i].c;
if(find(a)!=find(b))
{
p[find(a)]=find(b);
res=max(res,c);
cnt++;
if(cnt==n-k+1)
{
return;
}
}
}
}
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
p[i]=i;
for(int i=1;i<=n;i++)
{
cin>>d[i].x>>d[i].y;
}
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
ans++;
s[ans].a=i;
s[ans].b=j;
s[ans].c=dis(d[i],d[j]);
}
}
kruskal();
printf("%.2lf",res);
return 0;
}
8 lgP1967
开始觉得没想错,一画图觉得我忘了一种情况,敌人通过敌人没占领的点也可以到别的敌人所在点。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1e5+10,M=1e6+10;
const int INF=0x3f3f3f3f;
typedef long long LL;
int p[N],n,m,k,cnt;
LL res;
bool st[N];
struct bian
{
int a,b,c;
}s[M];
bool cmp(bian a,bian b)
{
return a.c<b.c;
}
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
void kruskal()
{
sort(s,s+m+1,cmp);
for(int i=0;i<m;i++)
{
int a=s[i].a,b=s[i].b,c=s[i].c;
if(find(a)!=find(b))
{
p[find(a)]=find(b);
if(c==INF)
{
cout<<res;
return;
}
res+=c;
cnt++;
}
}
}
int main()
{
LL sum=0;
cin>>n>>k;
m=n-1;
for(int i=0;i<=m;i++)
p[i]=i;
while (k -- )
{
int x;
cin>>x;
st[x]=true;
}
for(int i=0;i<m;i++)
{
int a,b,c;
cin>>a>>b>>c;
s[i].a=a;
s[i].b=b;
if(st[a]&&st[b])
s[i].c=c;
else
s[i].c=INF;
sum+=c;
}
if(k==n)
{
cout<<sum;
return 0;
}
for(int i=0;i<m;i++)
cout<<s[i].c<<' ';
kruskal();
return 0;
}
建最大边,总的减去就是不符合的最小边。改了之后还是有错,错在并查集上。
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=1e6+10;
const int INF=0x3f3f3f3f;
typedef long long LL;
int p[N],n,k,cnt;
LL ans;
bool st[N];
struct bian
{
int a,b,c;
}s[M];
bool cmp(const bian &a,const bian &b)
{
return a.c>b.c;
}
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
p[i]=i;
while (k -- )
{
int x;
cin>>x;
st[x]=true;
}
for(int i=1;i<=n-1;i++)
{
cin>>s[i].a>>s[i].b>>s[i].c;
ans+=s[i].c;
}
sort(s+1,s+n,cmp);
for(int i=1;i<=n-1;i++)
{
int r1=find(s[i].a),r2=find(s[i].b);
if(st[r1] && st[r2])//都并入敌人集合,这条边不建
continue;
p[r1]=r2;//为什么将r1并入r2中就错???
if(st[r1]==true)//并入敌人集合后也算敌人
st[r2]==true;
else if(st[r2]==true)
st[r1]=true;
ans-=s[i].c;
}
printf("%lld",ans);
return 0;
}

注意不要并错集合,并查集只在根节点上有意义。上面把r2并入r1中就是ac代码如下。
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=1e6+10;
const int INF=0x3f3f3f3f;
typedef long long LL;
int p[N],n,k,cnt;
LL ans;
bool st[N];
struct bian
{
int a,b,c;
}s[M];
bool cmp(bian a,bian b)
{
return a.c>b.c;
}
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
void kruskal()
{
sort(s+1,s+n,cmp);
for(int i=1;i<=n-1;i++)
{
if(!(st[find(s[i].a)] && st[find(s[i].b)]))
{
p[find(s[i].b)]=find(s[i].a);
st[find(s[i].a)]=(st[find(s[i].a)]||st[find(s[i].b)]);
ans-=s[i].c;
}
}
}
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
p[i]=i;
while (k -- )
{
int x;
cin>>x;
st[x]=true;
}
for(int i=1;i<=n-1;i++)
{
cin>>s[i].a>>s[i].b>>s[i].c;
ans+=s[i].c;
}
kruskal();
printf("%lld",ans);
return 0;
}

9 lgP1536
开始思路是将连通的边都标记为0,不太容易标记。
索性直接kruskal判断已连通的边的个数cnt,如果等于n-1说明n个点已连通无需再添边;否则添加n-1-cnt条边才能连通。
#include <bits/stdc++.h>
using namespace std;
const int N=1010,M=2e6+10;
int p[N],n,m,res,cnt;
bool st[N][N];
struct bian
{
int a,b,c;
}s[M];
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
bool cmp(bian a,bian b)
{
return a.c<b.c;
}
int main()
{
while(scanf("%d%d",&n,&m))
{
cnt=0;
if(n==0)
break;
for(int i=1;i<=n;i++)
p[i]=i;
for(int i=1;i<=m;i++)
cin>>s[i].a>>s[i].b;
sort(s+1,s+1+m,cmp);
for(int i=1;i<=m;i++)
{
int a=s[i].a,b=s[i].b,c=s[i].c;
if(find(a)!=find(b))
{
p[find(a)]=find(b);
cnt++;
}
}
if(cnt==n-1)
{
cout<<0<<endl;
continue;
}
else
cout<<n-1-cnt<<endl;
}
return 0;
}
10 lgP2916
这题挺有意思,点上的权值怎么办?我们注意到每经历一条边去一个农场再回来时边权可以化成:两端点权值和+边权*2,别忘了起点在开始时就需要多加一次,最小树不变起点选最小的就行。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=10010,M=2e5+10;
int p[N],n,m,res,cnt,w[N],minw=0x3f3f3f3f;
struct bian
{
int a,b,c;
}s[M];
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
bool cmp(bian a,bian b)
{
return a.c<b.c;
}
void kruskal()
{
sort(s+1,s+1+m,cmp);
for(int i=1;i<=m;i++)
{
int a=s[i].a,b=s[i].b,c=s[i].c;
if(find(a)!=find(b))
{
p[find(b)]=find(a);
res+=c;
cnt++;
if(cnt==n-1)
return;
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
p[i]=i;
for(int i=1;i<=n;i++)
{
cin>>w[i];
minw=min(minw,w[i]);//最小树不变,多劝一次的起点当然选选最小点
}
for(int i=1;i<=m;i++)
{
cin>>s[i].a>>s[i].b>>s[i].c;
s[i].c=s[i].c*2+w[s[i].a]+w[s[i].b];
}
kruskal();
cout<<res+minw;
return 0;
}
11 lgP1340
吸氧之后过了。
数据很小对每一步prim一次,注意上一步最短边的状态不一定能用,因为之后可能有更小的边输入。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=210,M=4e4+10;
int dist[N],n,m,res,cnt;
bool st[N];
int g[N][N];
bool prim()
{
memset(dist,0x3f,sizeof dist);
for(int i=1;i<=n;i++)
{
int t=-1;
for(int j=1;j<=n;j++)
{
if(st[j]==false &&(t==-1 || dist[t]>dist[j]))
t=j;
}
st[t]=true;
if(i!=1 && dist[t]==0x3f3f3f3f)
return false;
if(i!=1)
res+=dist[t];
for(int j=1;j<=n;j++)
{
if(dist[j]>g[t][j])
dist[j]=g[t][j];
}
}
return true;
}
int main()
{
cin>>n>>m;
memset(g,0x3f,sizeof g);
while (m -- )
{
memset(st,false,sizeof st);
res=0,cnt=0;
int a,b,c;
cin>>a>>b>>c;
if(g[a][b]>c)
g[a][b]=g[b][a]=c;
if(prim())
cout<<res<<endl;
else
cout<<-1<<endl;
}
return 0;
}
12 lgP1265
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdio>
using namespace std;
const int N=5050,M=5e7+10;
int p[N],n,m,cnt,ans=0;
double res;
struct bian
{
int a,b;
double c;
}s[M];
struct node
{
int x,y;
}d[N];
bool cmp(bian a,bian b)
{
return a.c<b.c;
}
double dis(node a,node b)
{
return (double)sqrt((double)pow((a.x-b.x),2)+(double)pow((a.y-b.y),2));;
}
int find(int x)
{
if(p[x]!=x)p[x]=find(p[x]);
return p[x];
}
void kruskal()
{
sort(s+1,s+1+ans,cmp);
for(int i=1;i<=ans;i++)
{
int a=s[i].a,b=s[i].b;
double c=s[i].c;
if(find(a)!=find(b))
{
p[find(b)]=find(a);
res+=c;
cnt++;
if(cnt==n-1)
printf("%.2lf",res);
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++) p[i]=i;
for(int i=1;i<=n;i++)
cin>>d[i].x>>d[i].y;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
ans++;
s[ans].a=i;
s[ans].b=j;
s[ans].c=dis(d[i],d[j]);
}
}
kruskal();
return 0;
}

竟然卡空间5000*5000。
5000 *5000用于存边,而空间限制不许存边,又因为是稠密图所以改用prim。prim是找点,所以边不需要邻接矩阵存,直接用两点求即可。
#include <bits/stdc++.h>
using namespace std;
const int N=5050;
int n;
double dist[N],res;
bool st[N];
struct node
{
int x,y;
}d[N];
double dis(node a,node b)
{
return (double)sqrt((double)pow((a.x-b.x),2)+(double)pow((a.y-b.y),2));;
}
void prim()
{
for(int i=1;i<=n;i++)
dist[i]=100000000;
for(int i=1;i<=n;i++)
{
int t=-1;
for(int j=1;j<=n;j++)
{
if(st[j]==false && (t==-1 || dist[t]>dist[j]))
t=j;
}
st[t]=true;
if(i!=1 && dist[t]==100000000)
return;
if(i!=1)
res+=dist[t];
for(int j=1;j<=n;j++)
{
double dd=dis(d[t],d[j]);
if(dist[j]>dd)
dist[j]=dd;
}
}
printf("%.2lf",res);
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>d[i].x>>d[i].y;
prim();
return 0;
}
本文详细介绍了如何运用Kruskal算法解决一系列图论问题,包括最小生成树、最大森林、最短路径等。通过实例分析,展示了在不同场景下Kruskal算法的应用,并给出了相应的C++实现代码,帮助读者深入理解Kruskal算法及其在实际问题中的应用。
1079

被折叠的 条评论
为什么被折叠?



