树的定义:连通无回路的无向图是一棵树。

有关树的问题:

1、最小生成树。

2、次小生成树。

3、有向图的最小树形图。

4、LCA(树上两点的最近公共祖先)。

5、树的最小支配集、最小点覆盖、最大独立集。

一、最小生成树

解决的问题是:求无向图中边权值之和最小的生成树。

算法有Kruskal和Prim。

Kruskal使用前向星和并查集实现,可以存储重边(平行边),时间复杂度是O(m log m  +  m),m是边的数量。

Prim使用邻接矩阵建图,不可以存储重边(平行边),如果出现重边,存储的是权值最小的那一条,时间复杂度为O(n*n), n是顶点的数量。使用邻接表建图可能会提高效率。

一般情况下,题目都是比较裸的。难度为易。

模版:建好图以后,直接调用,输出。

prim

 const int maxn=,INF=0x3f3f3f3f;
int dist[maxn],Map[maxn][maxn],pre[maxn];
//向外延伸的最短边长,记录图信息,记录连接信息
bool p[maxn];//1表示点已经在树的,0表示点在树外
bool f[maxn][maxn];
int Prim(int n)
{
int i,j,k,Min,ans=;
for(i=;i<=n;i++)
{
p[i]=;
dist[i]=Map[][i];
pre[i]=;
}
dist[]=;
p[]=;
for(i=;i<n;i++)
{
Min=INF;
k=;
for(j=;j<=n;j++)
{
if(!p[j]&&dist[j]<Min)
{
Min=dist[j];
k=j;
}
}
if(k==) return -;//G不连通
ans+=Min;
p[k]=;
for(j=;j<=n;j++)
{
if(!p[j]&&Map[k][j]!=INF&&dist[j]>Map[k][j])
{
dist[j]=Map[k][j];
pre[j]=k;
}
}
}
return ans;
}

Kruskal

 const int N=,M=;
int f[N],r[N];
struct node
{
int x, y, len;
}e[M];
int cmp(const node &a,const node &b)
{
return a.len<b.len;
}
void Make_Set(int n)
{
for(int i=;i<=n;i++)
{
f[i]=i;
r[i]=;
}
}
void Link(int a, int b)
{
if (r[a] > r[b]) {f[b] = a; r[a]+=r[b];}
else {f[a] = b; r[b]+=r[a];}
}
int Find_Set(int a)
{
if (a == f[a]) return a;
else return f[a] = Find_Set(f[a]);
}
int Kruskal(int n,int m)
{
int i, j, k, s=,cn=;
Make_Set(n);
sort(e,e+m,cmp);
for(i=;i<m;i++)
{
j=Find_Set(e[i].x);
k=Find_Set(e[i].y);
if(j!=k) {
Link(j,k);
s+=e[i].len;
cn++;
}
if(cn==n-) break;
}
if(cn<n-) return -;
return s;
}

二、次小生成树

1、最朴素的办法就是暴力枚举。求一遍最小生成树以后,依次删除最小生成树上的边,求此时的最小生成树,值最小的就是我们所求的。总共要运行 n次最小生成树算法,时间复杂度为n倍最小生成树算法的复杂度。有些题目能AC。暴力枚举在没有好方法时,也是一种非常好的方法。

2、我们知道,添加任意一条不在最小生成树上的边以后一定会形成一个环。找到环上除了(u,v)以外的权值最大的边,把它删掉,那么此时生成树就可能是次小生成树。基于这种思想:首先求出原图最小生成树,权值之和为MST。在执行最小最小生成树算法的时候,记录任意两点(u,v)路径之间的最大权值,maxe[u][v]。注意,不是记录以u与v为起点和终点的边的最大值,是两点之间的路径,可能有多个点在它们之间。然后枚举每一条边,如果该边不是原本最小生成树上的边,就用MST - maxe[u][v] + Map[u][v],统计结果最小的。

需要知道的是次小生成树的权值和可以与最小生成树的权值和相等。

模版:

prim(直接贴poj1679的代码了)

 #include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std; const int maxn=,INF=0x3f3f3f3f;
int dist[maxn],Map[maxn][maxn],pre[maxn],maxe[maxn][maxn];
//向外延伸的最短边长,记录图信息,记录连接信息,最长边
bool vis[maxn];//1表示点已经在树的,0表示点在树外
bool f[maxn][maxn];//存在边为1,用过为0
int n,m;//顶点数目
int Prim()
{
int i,j,k,Min,ans=;
memset(vis,,sizeof(vis));
memset(f,,sizeof(f));
memset(maxe,,sizeof(maxe));
for(i=;i<=n;i++)
{
dist[i]=Map[][i];
pre[i]=;
}
dist[]=;
vis[]=;
pre[]=;
for(i=;i<n;i++)
{
Min=INF;
k=;
for(j=;j<=n;j++)
{
if(!vis[j]&&dist[j]<Min)
{
Min=dist[j];
k=j;
}
}
if(Min==INF) return -;//G不连通
vis[k]=;
ans+=Min;
f[pre[k]][k] = f[k][pre[k]]=;
for(j=;j<=n;j++)
{
if(vis[j]) maxe[k][j]=maxe[j][k]=max(maxe[j][pre[k]],dist[k]);
if(!vis[j]&&dist[j]>Map[k][j])
{
dist[j]=Map[k][j];
pre[j]=k;
}
}
}
return ans;
}
void init()
{
for(int i=;i<=n;i++)
for(int j=;j<=n;j++)
{
if(i==j)Map[i][j]=;
else Map[i][j]=INF;
}
}
int main()
{
//freopen("test.txt","r",stdin);
int i,j,k,a,b,c,ca;
scanf("%d",&ca);
while(ca--)
{
scanf("%d%d",&n,&m);
init();
for(i=;i<m;i++)
{
scanf("%d%d%d",&a,&b,&c);
Map[a][b]=Map[b][a]=c;
}
int ans=Prim();
if(ans==-) {printf("Not Unique!\n");continue;}
int res=INF;
for(i=;i<=n;i++)
{
for(j=i+;j<=n;j++)
{
if(!f[i][j]&&Map[i][j]!=INF)
{
res=(res,ans+Map[i][j]-maxe[i][j]);
}
}
}
if(res==ans) printf("Not Unique!\n");
else printf("%d\n",ans);
}
return ;
}

kruskal

 //O(mlogm+n^2)
const int N=,M=,INF=0x3f3f3f3f;
int f[N],r[N];
int Find(int x)
{
if(x==f[x]) return x;
return f[x]=Find(f[x]);
}
void Link(int x,int y)
{
int a=Find(x), b=Find(y);
if(a!=b)
{
f[b]=a;
r[a]+=r[b];
}
}
struct node
{
int u,v,w;
bool select;
}edge[M];
bool cmp(const node &a, const node &b)
{
if(a.w!=b.w) return a.w<b.w;
if(a.u!=b.u) return a.u<b.u;
return a.v<b.v;
}
struct node1
{
int to,next;
}e[N];
int cnt,tot,head[N],en[N],maxe[N][N];
//e的边数、头结点、尾结点、两点在mst中的最大边
void Kruskal(int n,int m)
{
int k=, i,j,t;
for(cnt=;cnt<n;cnt++)
{
e[cnt].to=cnt+;
e[cnt].next=head[cnt+];
en[cnt+]=cnt;
head[cnt+]=cnt;
}
sort(edge,edge+m,cmp);
for(i=;i<m;i++)
{
if(k==n-) break;
if(edge[i].w<) continue;
int a=Find(edge[i].u), b=Find(edge[i].v);
if(a!=b)
{
for(j=head[a];j!=-;j=e[j].next){
for(t=head[b];t!=-;t=e[t].next){
int u=e[j].to, v=e[t].to;
maxe[u][v]=maxe[v][u]=edge[i].w;
}
}
e[en[b]].next=head[a];
en[b]=en[a];
Link(a,b);
k++;
edge[i].select=;
}
}
}
void addedge(int u,int v,int w)
{
edge[tot].u=u;edge[tot].v=v;edge[tot].w=w;tot++;
}
void init()
{
tot=;
memset(head,-,sizeof(head));
memset(en,-,sizeof(en));
for(int i=;i<N;i++){
edge[i].select=;
f[i]=i;
r[i]=;
}
}
int main()
{
//freopen("test.txt","r",stdin);
int mst,secmst;
Kruskal(n,m);
mst=;
for(i=;i<m;i++){
if(edge[i].select) mst+=edge[i].w;
}
secmst=INF;
for(i=;i<m;i++){
if(!edge[i].select)
secmst=min(secmst,mst+edge[i].w-maxe[edge[i].u][edge[i].u]]);
} return ;
}

算法的不同,当然也继承了该算法的特点。Prim不能存重边。这是需要注意的。

题目:Hdu2489  poj1679

hdu4081http://www.cnblogs.com/Potato-lover/p/3938482.html

三、有向图的最小树形图

解决的问题:一水源给菜地供水,水只能从高处向低处流,修水渠需要一定的花费,问怎样才能使得花费最低。

边是有方向的,与最小生成树不同,这可以理解为有向图的最小生成树。

算法是朱刘算法,难得的以国人名字命名的算法。

邻接矩阵建图,时间复杂度为O( n*n*n )。 hdu4009用这种方法超时了。

前向星建图,时间复杂度为O( n*m )。

朱刘算法的两种实现可以看我的博客 http://www.cnblogs.com/Potato-lover/p/3942321.html

题目:hdu4009 、 hdu2121

四、LCA(树上两点的最近公共祖先)

解决的问题:求树上的任意两个点之间的最短距离。

用最短路的方法是行不通的,复杂度太高。所以才有解决LCA的专门的算法。

我只会离线的Tarjan(图拉)算法。 时间复杂度为O(m+q),m是边数,q是问题的个数。

我的博客里已经有相关的介绍了。http://www.cnblogs.com/Potato-lover/category/613545.html

题目:poj1330 (hdu2586)

五、树的最小支配集、最小点覆盖、最大独立集

注意:是树上的,图中一定不能有圈。

都是基于深度优先搜索。

最小支配集:对于图G=(V,E),从V取尽量少的点组成一个集合,使得V中剩余的点都与取出来的点有边相连。

最小点覆盖:对于图G=(V,E),从V取尽量少的点组成一个集合,使得E中所有的边都与取出来的点相连。

最大独立集:对于图G=(V,E),从V取尽量多的点组成一个集合,使得这些点之间没有边相连。

时间复杂度都是O(n)。

这方面的题目似乎不曾出现,但是作为图论的学习者,有必须会。

实现的算法很相似。

顶点下标从1开始的。

 /*newpos[i]表示深度优先遍历序列的第i个点是哪个点,
now表示当前深度优先遍历序列已经有多少个点了。select[]用于
深度优先遍历的判重,p[i]表示点i的父节点的编号。对于greedy(),
s[i]为1表示第i个点被覆盖。se[i]表示点i属于要求的点集*/ const int N = ;
struct node
{
int to, w, next;
}e[N*N];
int head[N],tot,now;
int p[N], newpos[N] ;
bool select[N];
bool s[N],se[N];
//深度优先遍历,得到深度优先遍历序列
void dfs(int x)
{
newpos[now++] = x;
int k;
for(k=head[x]; k!=-; k=e[k].next)
{
if(!select[e[k].to])
{
select[e[k].to]= ;
p[e[k].to]=x;
dfs(e[k].to);
}
}
}
//最小支配集
int greedy1()
{
memset(s,,sizeof(s));
memset(se,,sizeof(se));
int ans=;
int i;
for(i=now-; i>=; i--)
{
int t=newpos[i];
if(!s[t])
{
if(!se[p[t]])
{
se[p[t]]=;
ans++;
}
s[t] = ;
s[p[t]] = ;
s[p[p[t]]] = ;
}
}
return ans;
}
//最小点覆盖
int greedy2()
{
memset(s,,sizeof(s));
memset(se,,sizeof(se));
int ans=;
int i;
for(i=now-; i>=; i--)
{
int t=newpos[i];
if(!s[t]&&!s[p[t]])
{
se[p[t]]=;
ans++;
s[t] = ;
s[p[t]] = ;
}
}
return ans;
}
//最大独立集
int greedy3()
{
memset(s,,sizeof(s));
memset(se,,sizeof(se));
int ans=;
int i;
for(i=now-; i>=; i--)
{
int t=newpos[i];
if(!s[t])
{
se[t]=;
ans++;
s[t] = ;
s[p[t]] = ;
}
}
return ans;
}
void addedge(int i,int j)
{
e[tot].to=j;e[tot].next=head[i];head[i]=tot++;
}
void init()
{
tot=now=;
memset(head,-,sizeof(head));
memset(select, , sizeof(select));
}
int main()
{
/* 读入图*/
select[]=;
p[]=;
dfs();
printf("%d\n",greedy());
return ;
}

树的问题小结(最小生成树、次小生成树、最小树形图、LCA、最小支配集、最小点覆盖、最大独立集)的更多相关文章

  1. 训练指南 UVALive - 5713(最小生成树 + 次小生成树)

    layout: post title: 训练指南 UVALive - 5713(最小生成树 + 次小生成树) author: "luowentaoaa" catalog: true ...

  2. URAL 1416 Confidential (最小生成树+次小生成树)

    Description Zaphod Beeblebrox - President of the Imperial Galactic Government. And by chance he is a ...

  3. 最小生成树(次小生成树)(最小生成树不唯一) 模板:Kruskal算法和 Prim算法

    Kruskal模板:按照边权排序,开始从最小边生成树 #include<algorithm> #include<stdio.h> #include<string.h> ...

  4. [ An Ac a Day ^_^ ] [kuangbin带你飞]专题八 生成树 UVA 10600 ACM Contest and Blackout 最小生成树+次小生成树

    题意就是求最小生成树和次小生成树 #include<cstdio> #include<iostream> #include<algorithm> #include& ...

  5. POJ 1679 The Unique MST 【最小生成树/次小生成树模板】

    The Unique MST Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 22668   Accepted: 8038 D ...

  6. 最小生成树&&次小生成树

    对于一个边上具有权值的图来说,其边权值和最小的生成树叫做图G的最小生成树 求无向图最小生成树主要有prim和kruskal两种算法 1.prim 将点集V分成Va和Vb两部分,Va为已经连入生成树的点 ...

  7. hdu 4081 Qin Shi Huang's National Road System 树的基本性质 or 次小生成树思想 难度:1

    During the Warring States Period of ancient China(476 BC to 221 BC), there were seven kingdoms in Ch ...

  8. HDU 4081 Qin Shi Huang&#39;s National Road System(最小生成树/次小生成树)

    题目链接:传送门 题意: 有n坐城市,知道每坐城市的坐标和人口.如今要在全部城市之间修路,保证每一个城市都能相连,而且保证A/B 最大.全部路径的花费和最小,A是某条路i两端城市人口的和,B表示除路i ...

  9. (最小生成树 次小生成树)The Unique MST -- POJ -- 1679

    链接: http://poj.org/problem?id=1679 http://acm.hust.edu.cn/vjudge/contest/view.action?cid=82831#probl ...

随机推荐

  1. DOCKER - J2EE中容器:WEB容器、EJB容器

    转自:http://www.voidcn.com/article/p-yizkqdxp-zg.html

  2. appium的滑动

    #coding = utf-8from appium import webdriverimport time'''1.手机类型2.版本3.手机的唯一标识 deviceName4.app 包名appPa ...

  3. 移动端rem.js使用方法

    下面的代码一是我根据rem的使用经验,自己写的一个rem.js,发现很好用,能适用所有移动端h5页面的自适应需求: 代码一: ``` window.onload = function(){ /*720 ...

  4. PAT 1075. PAT Judge

    The ranklist of PAT is generated from the status list, which shows the scores of the submittions. Th ...

  5. 0316 【案例】MySQL count操作优化案例一则

      转自http://blog.itpub.net/22664653/viewspace-1791124/ 一 背景 某业务的数据库定期报 thread_runing 飙高,通定位发现一个慢查询sql ...

  6. binlog

    binlog基本定义:二进制日志,也成为二进制日志,记录对数据发生或潜在发生更改的SQL语句,并以二进制的形式保存在磁盘中: 作用:MySQL的作用类似于Oracle的归档日志,可以用来查看数据库的变 ...

  7. ZOJ 3717

    这题是二分+2SAT. 总结一下SAT题的特征.首先,可能会存在二选一的情况,然后会给出一些矛盾.据这些矛盾加边,再用SAT判定. 这一道题好像不能直接用printf("%0.3lf&quo ...

  8. 逻辑斯蒂回归3 -- 最大熵模型之改进的迭代尺度法(IIS)

    声明: 1,本篇为个人对<2012.李航.统计学习方法.pdf>的学习总结,不得用作商用.欢迎转载,但请注明出处(即:本帖地址). 2,因为本人在学习初始时有非常多数学知识都已忘记.所以为 ...

  9. C++二叉树笔试题

    #include <iostream> #include <stack> #include <queue> using namespace std; templat ...

  10. 用SecureCRT在linux系统下载文件

    使用sz命令 说明如下: sz --helpsz version 0.12.20Usage: sz [options] file ...   or: sz [options] -{c|i} COMMA ...