生成树:
一个连通图的生成树是一个极小连通子图,它含有图中全部顶点,但只有构成一棵树的 n-1 条边。
如果在一棵生成树上添加一条边,必定构成一个环,有n-1条边的图不一定都是生成树
最小生成树:
图的所有生成树中具有边上的权值之和最小的树
按照生成树的定义,n个顶点的连通图的生成树有n个顶点、n-1条边
因此,构造最小生成树的准则有三条:
(1)必须只使用该图中的边来构造最小生成树;
(2)必须使用且仅使用n-1条边来连接图中的n个顶点;
(3)不能使用产生回路的边。
prim算法堆优化模板:
#include<bits/stdc++.h>
#define ll long long
#define MAXN 200005
using namespace std;
int n, m;
struct node
{
int v, w;
bool operator<(const node &dd)const{
return w>dd.w;
}
};
vector<node> q[MAXN];
bool vis[MAXN];
ll ans;
void prim()
{
priority_queue<node> que;
while(!que.empty())
que.pop();
ans = 0;
memset(vis, false, sizeof(vis));
for(int i=0; i<q[1].size(); i++)
que.push(q[1][i]);
vis[1]=true;
int edge=n-1;
node cur;
while(edge--)
{
cur = que.top();
que.pop();
if(vis[cur.v]==true)
{
while(vis[cur.v])
{
cur=que.top();
que.pop();
}
}
ans = ans+cur.w;
vis[cur.v]=true;
for(int i=0; i<q[cur.v].size(); i++)
{
if(vis[ q[cur.v][i].v ]==false)
que.push(q[cur.v][i]);
}
}
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
int i,j,u,v,w;
for(i=0; i<=n; i++)
q[i].clear();
for(i=0; i<m; i++)
{
scanf("%d %d %d",&u,&v,&w);
q[u].push_back(node{v,w});
q[v].push_back(node{u,w});
}
prim();
printf("%lld\n",ans);
}
return 0;
}
kruskal:
通常稀疏图用kruskal算法,kruskal算法用到的是边
该算法用到了并查集,没学过的要先学习并查集
首先对所以边进行长度排序
将第一条边加入
然后依次按长度顺序加入,用并查集查找将要加入的边.
看它是否会让图成环,如果成环(边上的两点都已经在图中)就舍去,继续查找找到所有的点
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
int n,m,f[101];
struct edge
{
int a,b,lg;
}tree[10240];
int cmp(edge x,edge y)
{
return x.lg<y.lg;
}
void init()
{
for(int i=0;i<MAXN;i++)
f[i]=i;
}
int find(int x)
{
if(x!=f[x])
f[x]=find(f[x]);
return f[x];
}
int mix(int x,int y)
{
int fx=find(x),fy=find(y);
if(fx!=fy)
f[fy]=fx;
else return 0;
return 1;
}
void kru()
{
int sum=0,num=0;
sort(tree,tree+m,cmp);
for(int i=1;i<=m&&num!=n-1;i++)
{
if(mix(tree[i].a,tree[i].b))
{
num++;
sum+=tree[i].lg;
}
}
if(num!=n-1)
printf("?\n");
else printf("%d\n",sum);
}
int main()
{
while(scanf("%d%d",&m,&n)&&m!=0)
{
memset(tree,0,sizeof(tree));
for(int i=1;i<=n;i++)
f[i]=i;
for(int i=1;i<=m;i++)
scanf("%d%d%d",&tree[i].a,&tree[i].b,&tree[i].lg);
kru();
}
return 0;
}
hdu-1233
#include<bits/stdc++.h>
#define MAXN 5005
using namespace std;
int f[MAXN];
int n;
struct node
{
int a,b,val;
}tree[MAXN];
bool cmp(node a,node b)
{
return a.val<b.val;
}
int find(int x)
{
if(f[x]!=x)
{
f[x]=find(f[x]);
}
return f[x];
}
int unite(int a,int b)
{
int aa=find(a);
int bb=find(b);
if(aa==bb)
return 0;
else{
f[aa]=bb;
return 1;
}
}
int main()
{
while(scanf("%d",&n)&&n)
{
for(int i=1;i<5000;i++)
f[i]=i;
int m=n*(n-1)/2;
for(int i=0;i<m;i++)
scanf("%d%d%d",&tree[i].a,&tree[i].b,&tree[i].val);
sort(tree,tree+m,cmp);
int sum=0,num=0;
for(int i=0;i<m;i++)
{
if(unite(tree[i].a,tree[i].b))
{
sum+=tree[i].val;
num++;
}
if(num==n-1)
{
break;
}
}
printf("%d\n",sum);
}
return 0;
}
HDU - 4786
题意:
给定n个点,m条边,其中有若干条白边,问图中是否存在一个最小生成树,其白边的数目是个斐波那契数
解析:
分别求最小生成树a和最大生成树b
只要有一个斐波那契数在[a,b]中,Yes
#include<bits/stdc++.h>
#define MAXN 200005
using namespace std;
int ff[MAXN],f[MAXN];
int n,m,kk;
struct node
{
int a,b,lg;
}tree[MAXN];
bool cmp(node x,node y)
{
return x.lg<y.lg;
}
bool cmp2(node x,node y)
{
return x.lg>y.lg;
}
int find(int x)
{
if(x!=f[x])
f[x]=find(f[x]);
return f[x];
}
int mix(int x,int y)
{
int fx=find(x),fy=find(y);
if(fx!=fy)
f[fy]=fx;
else return 0;
return 1;
}
int solve()
{
ff[1]=1;
ff[2]=1;
for(int i=3;i<=100000;i++)
{
ff[i]=ff[i-2]+ff[i-1];
if(ff[i]>100000)
break;
}
}
int kru(int sig)
{
for(int i=0;i<=n;i++)
f[i]=i;
int sum=0,num=0;
if(sig==1)
sort(tree+1,tree+m+1,cmp);
else
sort(tree+1,tree+m+1,cmp2);
for(int i=1;i<=m&&num!=n-1;i++)
{
if(mix(tree[i].a,tree[i].b))
{
num++;
sum+=tree[i].lg;
}
}
if(num!=n-1)
return -1;
else
return sum;
}
int main()
{
solve();
int t,cas=1;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&tree[i].a,&tree[i].b,&tree[i].lg);
int a=kru(1);
int b=kru(0);
if(a==-1||b==-1)
{
printf("Case #%d: No\n",cas++);
}
else{
int flag=0;
for(int i=1;i<=25;i++)
{
if(ff[i]>=a&&ff[i]<=b)
{
flag=1;
printf("Case #%d: Yes\n",cas++);
break;
}
}
if(flag==0)
printf("Case #%d: No\n",cas++);
}
}
return 0;
}
另一个ac代码:
#include<bits/stdc++.h>
#define MAXN 200005
using namespace std;
int ff[MAXN],f[MAXN];
int n,m,kk;
struct edge
{
int a,b,lg;
}tree[MAXN];
int cmp(edge x,edge y)
{
return x.lg<y.lg;
}
int find(int x)
{
if(x!=f[x])
f[x]=find(f[x]);
return f[x];
}
int mix(int x,int y)
{
int fx=find(x),fy=find(y);
if(fx!=fy)
f[fy]=fx;
else return 0;
return 1;
}
int solve()
{
ff[1]=1;
ff[2]=1;
for(int i=3;i<=100000;i++)
{
ff[i]=ff[i-2]+ff[i-1];
if(ff[i]>100000)
break;
}
}
int kru()
{
for(int i=0;i<=n;i++)
f[i]=i;
int sum=0,num=0;
sort(tree+1,tree+m+1,cmp);
for(int i=1;i<=m&&num!=n-1;i++)
{
if(mix(tree[i].a,tree[i].b))
{
num++;
sum+=tree[i].lg;
}
}
if(num!=n-1)
return 0;
else{
if(sum<=kk)
return 1;
else
return 0;
}
}
int main()
{
solve();
int t,cas=1;
scanf("%d",&t);
while(t--)
{
kk=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&tree[i].a,&tree[i].b,&tree[i].lg);
kk+=tree[i].lg;
}
if(kk==0)
{
printf("Case #%d: No\n",cas++);
continue;
}
kk=min(kk,n-1);
for(int i=25;i>=1;i--)
{
if(ff[i]<=kk)
{
kk=ff[i];
break;
}
}
int g=kru();
if(g)
printf("Case #%d: Yes\n",cas++);
else
printf("Case #%d: No\n",cas++);
}
return 0;
}
题意
给你n nn个点m mm条边的无向联通图,找出一棵生成树,使度最大的点的度最大。
1≤n,m≤105 1 \leq n,m \leq 10^51≤n,m≤10
5
解析:
度最大的点一定是原图度最大的点,记录度数,从度最大的点开始遍历生成树
ac:
#include<bits/stdc++.h>
#define MAXN 200005
using namespace std;
typedef pair <int, int> pii;
vector<int> mp[MAXN];
vector<pii> ans;
int in[MAXN]={0};
int vis[MAXN];
void bfs(int st)
{
vis[st]=1;
queue<int> que;
que.push(st);
while(!que.empty())
{
int u=que.front();
que.pop();
for(int i=0;i<mp[u].size();i++)
{
int v=mp[u][i];
if(vis[v]==0)
{
vis[v]=1;
que.push(v);
ans.push_back(pii(u,v));
}
}
}
}
int main()
{
int n,m,u,v;
scanf("%d%d",&n,&m);
memset(vis,0,sizeof(vis));
for(int i=1;i<=m;i++)
{
scanf("%d%d",&u,&v);
mp[u].push_back(v);
mp[v].push_back(u);
in[u]++;
in[v]++;
}
int maxs=0,st;
for(int i=1;i<=n;i++)
{
if(in[i]>maxs)
{
maxs=in[i];
st=i;
}
}
bfs(st);
for(int i=0;i<ans.size();i++)
printf("%d %d\n",ans[i].first,ans[i].second);
return 0;
}
http://acm.hdu.edu.cn/showproblem.php?pid=6724
题意:
问你能否找到k个生成树
解析:
直接遍历k次,看是否标记了n个点,遍历后要标记边
ac:
#include<bits/stdc++.h>
#define MAXN 605
using namespace std;
int to[MAXN<<2],next1[MAXN<<2],head[MAXN<<2];
int tot=0;
int edg[MAXN<<2];
int vis[MAXN];
void add(int u,int v)
{
to[++tot]=v;
next1[tot]=head[u];
head[u]=tot;
}
int cnt=0;
void dfs(int u)
{
for(int i=head[u];i;i=next1[i])
{
if(edg[i]==1)
continue;
int v=to[i];
if(vis[v]==0)
{
cnt++;
edg[i]=1;//用了的边标记
vis[v]=1;
dfs(v);
}
}
}
void init(){
tot=0;
memset(head,0,sizeof(head));
memset(next1,0,sizeof(next1));
memset(edg,0,sizeof(edg));
}
int main()
{
int n,m,t,k,a,b;
scanf("%d",&t);
while(t--)
{
init();
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&a,&b);
add(a,b);
add(b,a);
}
int flag=0;
for(int i=1;i<=k;i++)
{
memset(vis,0,sizeof(vis));
cnt=1;
vis[1]=1;
dfs(1);
if(cnt<n)
{
flag=1;
break;
}
}
if(flag)
printf("No\n");
else
printf("Yes\n");
}
return 0;
}
https://codeforces.com/contest/1245/problem/D
题意:
给定n个地方,要你给这些地方通电,通电可以选择两种方式
1.直接在这个地方建一个电站,花费ci
2.让这个地方i和一个已经建过电站的地方j相连,花费(ki+kj)*(dis(i,j))
解析:
直接最小生成树
建立n条ci边,与0点相连,最后必定需要在最小的ci位置建一个电站
ac:
#include<bits/stdc++.h>
#define pb push_back
#define ll long long
#define MAXN 2005
using namespace std;
int x[MAXN],y[MAXN];
int c[MAXN],k[MAXN];
int f[MAXN];
vector<int> vg;
struct ansa{
int x,y;
}aas[MAXN];
struct node
{
int u,v;
ll w;
friend bool operator <(node a,node b)
{
return a.w<b.w;
}
}ee[MAXN*MAXN];
void init()
{
for(int i=0;i<MAXN;i++)
f[i]=i;
}
int get(int x)
{
if(x!=f[x])
f[x]=get(f[x]);
return f[x];
}
void unite(int x,int y)
{
f[get(x)]=get(y);
}
void kru(int n)
{
init();
int tot=0;
ll maxs=0;
for(int i=1;i<=n;i++){
ee[++tot]=node{i,0,c[i]};
maxs=max(c[i]*1LL,maxs);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
ll g=(abs(x[i]-x[j])+abs(y[i]-y[j]))*1LL*(k[i]+k[j]);
if(g<=maxs)
ee[++tot]=node{i,j,g};
}
sort(ee+1,ee+tot+1);
ll ans=0;
int sum=0;
int tt=0;
for(int i=1;i<=tot&&sum<n;i++)
{
int x=get(ee[i].u);
int y=get(ee[i].v);
if(x!=y){
sum++;
ans+=ee[i].w;
unite(x,y);
if(ee[i].v==0){
vg.pb(ee[i].u);
}
else{
aas[++tt]={ee[i].u,ee[i].v};
}
}
}
printf("%lld\n",ans);
printf("%d\n",vg.size());
for(int i=0;i<vg.size();i++)
printf("%d ",vg[i]);
printf("\n");
printf("%d\n",tt);
for(int i=1;i<=tt;i++)
printf("%d %d\n",aas[i].x,aas[i].y);
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d",&x[i],&y[i]);
for(int i=1;i<=n;i++)
scanf("%d",&c[i]);
for(int i=1;i<=n;i++)
scanf("%d",&k[i]);
kru(n);
return 0;
}
https://codeforces.com/contest/1106/problem/D
题意:
从1出发,每次只能扩展一条边,求按字典序排的扩展顺序
解析:
类似prim算法,这里用set建边
ac:
#include<bits/stdc++.h>
#define MAXN 200005
using namespace std;
set<int> st[MAXN];
int ans[MAXN],cnt=1;
int vis[MAXN]={0};
void prim(int x,int n)
{
priority_queue<int,vector<int>,greater<int>> gg;
queue<int> que;
vis[1]=1;
ans[1]=1;
que.push(1);
int m=n-1;
while(m--)
{
int c=que.front();
que.pop();
for(auto it=st[c].begin();it!=st[c].end();it++)
{
if(vis[*it]==0){
vis[*it]=1;
gg.push(*it);
}
}
ans[++cnt]=gg.top();
que.push(gg.top());
gg.pop();
}
for(int i=1;i<=n;i++)
printf("%d ",ans[i]);
printf("\n");
}
int main()
{
int n,m,u,v;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
if(u==v){
continue;
}
st[u].insert(v);
st[v].insert(u);
}
prim(1,n);
return 0;
}