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

有关树的问题:

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. android全屏下的输入框未跟随软键盘弹起问题

    最近开发中遇到,全屏模式下输入框在底部不会跟随软键盘弹起.于是网上搜索了解决的方案.大致找到了两种方案. 第一种 定义好此类 public class SoftKeyBoardListener { p ...

  2. window.open方法被浏览器拦截的处理方式

    问题现象 当我们在一个 ajax 回调中执行 window.open 方法时,新页面会被浏览器拦截. 原因 在 Chrome 的安全机制里,非用户直接触发的 window.open 方法,是会被拦截的 ...

  3. call,apply,bind

    1.IE5之前不支持call和apply,bind是ES5出来的;2.call和apply可以调用函数,改变this,实现继承和借用别的对象的方法; 1 call和apply定义 调用方法,用一个对象 ...

  4. 2018 noip 考前临死挣扎

    基础算法 倍增 贪心 分块 二分 三分 数据结构 线段树 对顶堆 数学 质数 约数 同余 组合 矩阵乘法 图论 二分图判定以及最大匹配 字符串 Tire树 KMP 最小表示法 Hash Manache ...

  5. 【习题 4-4 UVA - 253】Cube painting

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 绕(x,y,z)三个轴旋转. 枚举x,y,z各4次的结果. (4次之后能还原.可以方便上一层枚举下一个情况.) [代码] #incl ...

  6. eclipse中 使用maven搭建ssh项目 思路复习(含有pom.xml)

    首先在web.xml中配置监听器 在服务器启动的时候 进行bean对象的创建(只会创建单例对象 dao service   多例对象action可不会创建  每个多例对象是不同的 创建了有什么意义呢 ...

  7. rmq的st算法模板题 nyoj 119

    士兵杀敌(三) 时间限制:2000 ms  |  内存限制:65535 KB 难度:5 描述 南将军统率着N个士兵,士兵分别编号为1~N,南将军经常爱拿某一段编号内杀敌数最高的人与杀敌数最低的人进行比 ...

  8. 介绍一个不错的服务器综合监控工具脚本集aspersa

    http://blog.csdn.net/jackyrongvip/article/details/9217869

  9. 韩国 DBA 博客

    http://mysqldba.tistory.com/ http://cafe.naver.com/mysqlpg http://cafe.naver.com/realmysql http://wi ...

  10. nutch的定时增量爬取

    译文来着: http://wiki.apache.org/nutch/Crawl 介绍(Introduction) 注意:脚本中没有直接使用Nutch的爬去命令(bin/nutch crawl或者是& ...