【算法每日一练]-图论 篇12 (tarjan篇)POJ3352道路建设 ,POJ2553图的底部 ,POJ1236校园网络 ,缩点

目录:

今天知识点

加边使得无向图图变成双连通图

找出度为0的强连通分量

加边使得有向图变成强连通图

将有向图转成DAG图进行dp

        

POJ3352:道路建设

        思路:

POJ2553:图的底部

思路:

POJ1236校园网络

思路:

缩点: 

思路:


        

POJ3352:道路建设

        
由于道路要维修,维修时候来回都不能走,现要在各个景点间建设新道路以便维修时候也能保证任何两个景点之间可以相互到达,求最少的新道路数量
任何一对景点间最多只能在它们之间有一条道路(没有重边)。道路一开始是联通的

输入:
3 3
1 2
2 3
1 3

10 12
1 2
1 3
1 4
2 5
2 6
5 6
3 7
3 8
7 8
4 9
4 10
9 10

        
思路:

无向图tarjan的效果就是把一张图划分成多个边双联通子图(通过集合号low划分),那么如果我们只关注集合号low(桥边)的话,那么相当于把一个边双联通分量看成了一个点(变成了一个DAG图),此时可以使用度来解决问题。

先求解边双连通分量,计算每个分量的度,然后通过加边再把原图变成双连通图即可。

计算无向图的度:只计算出度或入度就行,如果都计算的话会重复!

加边原理是这样的:
先统计叶节点个数为k,(k+1)/2就是要建的边数。因为在树中,给叶节点加边一定会产生环。

说一下tarjan后的操作 :

        for(int u=1;u<=n;u++)
			for(int i=head[u];i;i=e[i].next){
				int v=e[i].to;
				if(low[u]!=low[v]) deg[low[u]]++;//遍历新图的边(其实就是旧图的桥)
//有重边也要记录。low[u]就是连通分量号,每个连通分量中只有桥的点才有度
			}
		int leaf=0;
//		for(int i=1;i<=n;i++){
//			cout<<i<<' '<<deg[i]<<' '<<low[i]<<'\n';//看详情
//		}
		for(int i=1;i<=n;i++){//检查每个连通分量号的度(一定不为零)
			if(deg[i]==1) leaf++;//度是1就是叶子
		}
		cout<<(leaf+1)/2<<'\n';

 首先是缩点:low是连通分量号,把度统计到桥点身上(很像并查集中的缩点到祖宗点身上),注意我们这种缩点的过程肯定会遇到重边。此题中的重边是不能去掉的,否则叶节点会统计错误!!!然后统计度为1就是叶子就行。

对于重边:有时候必须要,有时候不影响,有时候也必须去重。要仔细分析!

#include <bits/stdc++.h>//无向图的桥
using namespace std;
const int maxn=1000+5;
int n,m;
int head[maxn],cnt;
struct node{int to,next;}e[maxn*2];
int low[maxn],dfn[maxn],deg[maxn],num;//deg是度(无向图没有入度和出度之分)

void add(int u,int v){ e[++cnt]=(node){v,head[u]};head[u]=cnt;}

void tarjan(int u,int fa){
	dfn[u]=low[u]=++num;//初始化
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;
		if(v==fa) continue;//不可以走父子边回去
		if(!dfn[v]){//没访问过就递归访问
			tarjan(v,u);
			low[u]=min(low[u],low[v]);//low是自己或子孙能走回的最小dfn
				
		}
		else{//可以从非父子边回去就要获取dfn值,就是该点能回到的最小dfn
			low[u]=min(low[u],dfn[v]);
		}
	}
}

void init(){
	memset(head,0,sizeof(head));
	memset(low,0,sizeof(low));
	memset(dfn,0,sizeof(dfn));
	memset(deg,0,sizeof(deg));
	cnt=num=0;
}

int main(){
	while(cin>>n>>m){
		init();
		int u,v;
		while(m--){
			cin>>u>>v;
			add(u,v);
			add(v,u);
		}
		tarjan(1,0);//求边双连通分量
		for(int u=1;u<=n;u++)
			for(int i=head[u];i;i=e[i].next){
				int v=e[i].to;//遍历新图的边(其实就是旧图的桥)
				if(low[u]!=low[v]) deg[low[u]]++;//建立新图的边,也就是原图的桥。为了防止重复统计,这里只统计出度
			}
		int leaf=0;
		for(int i=1;i<=n;i++){//检查每个连通分量号的度(一定不为零)
			if(deg[i]==1) leaf++;//度是1就是叶子
		}
		cout<<(leaf+1)/2<<'\n';
	}	
}

        

        

POJ2553:图的底部

        
有向图中若v可以到的任何一个u,u也可以到v,则v是一个sink点,图的底部是由所有sink点构成的,按顺序输出所有sink点编号,没有sink就输出一个空行

输::
3 3
1 3 2 3 3 1
2 1
1 2
0

思路:

有向图tarjan的效果就是把一张图划分成多个联通子图(通过集合号be划分),那么如果我们只关注集合号be的话,就相当于把一个连通分量看成了一个点(变成DAG图),此时可以使用度来解决问题。
                

我们的任务是只需要输出出度为0的连通分量中的所有点编号。不同于无向图,有向图的连通分量号我们用一个be数组存起来 ,然后对所有边进行判断是不是连接着两个分量,然后对新树中的边统计出度,输出出度为0的连通分量中的点编号。

for(int u=1;u<=n;u++)
	for(int i=head[u];i;i=e[i].next){//对所有边进行判断是不是连接着两个分量
		int v=e[i].to;
		if(be[u]!=be[v]){//有重边
			out[be[u]]++;//缩点,依然是只统计出度
		}
	}
int f=1;
for(int i=1;i<=n;i++){
	if(!out[be[i]]){//输出出度为0的连通分量中的点
		if(f) f=0;
		else cout<<" ";//一个数前面有个空格
		cout<<i; 
	}
}

完整代码

#include <bits/stdc++.h>
using namespace std;
const int maxn=5050;
bool ins[maxn];//标记是否在栈中
int n,m;
int head[maxn],be[maxn],out[maxn];//be是属于哪个连通分量,out是缩点的出度
int low[maxn],dfn[maxn],num,id,cnt;
stack <int> s;
struct node{int to,next;}e[maxn*2];

void add(int u,int v){ e[++cnt]=(node){v,head[u]};head[u]=cnt;}

void tarjan(int u){
	dfn[u]=low[u]=++num;//dfn访问序号,low是能走回到的最早的dfn
	ins[u]=1;
	s.push(u);//第一次访问节点时候入栈
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;
		if(!dfn[v]){//没访问过就递归访问
			tarjan(v);
			low[u]=min(low[u],low[v]);//获取孩子的最小的low值   
		}
		else if(ins[v]){//已经访问过且在栈中获取dfn号
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u]){//low[u]==dfn[u]时,则从栈中不断弹出节点,直到x出栈停止。弹出的节点就是同一个连通分量的
		int v;
		do{//一定要先执行再判断
			v=s.top();s.pop();
			be[v]=id;//把这些弹出的点标记同一个id号(连通分量号)
			ins[v]=0;
		}while(v!=u);//直到是自己为止
		id++;
	}
}

void init(){
	memset(head,0,sizeof(head));
	memset(low,0,sizeof(low));
	memset(ins,0,sizeof(ins));
	memset(dfn,0,sizeof(dfn));
	memset(out,0,sizeof(out));
	memset(be,0,sizeof(be));
	cnt=num=0;id=1;
}

int main(){
	while((cin>>n)&&n){//点数
		cin>>m;//边数
		init();
		int u,v;
		while(m--){
			cin>>u>>v;
			add(u,v);
		}
		for(int i=1;i<=n;i++){
			if(!dfn[i]) tarjan(i);//有向图
		}
		for(int u=1;u<=n;u++)
			for(int i=head[u];i;i=e[i].next){
				int v=e[i].to;
				if(be[u]!=be[v]){//有重边
					out[be[u]]++;//缩点
				}
			}
		int f=1;
		for(int i=1;i<=n;i++){
			if(!out[be[i]]){//输出出度为0的连通分量中的点
				if(f) f=0;
				else cout<<" ";//(输出格式罢了,不用在乎这里)
				cout<<i; 
			}
		}
	}	
}

        

        

POJ1236校园网络

        
每所学校都有一份发学校名单。计算至少先发给多少个学校才能使软件传到所有学校(任务1),计算至少增加多少扩展才能将软件发给任意学校结果都能传到所有学校(扩展就是将新成员引入一所学校的接收者名单)
5
2 4 3 0
4 5 0
0
0
1 0

        

思路:

        
任务1:每一个入度为0的连通分量都必须收到一个软件,计算个数。
任务2:每个连通分量必须既有入度也有出度,即入度为0的连通分量必须扩展一下,出度为0的连通分量必须也扩展一下(入度和出度对接,输出max就行)

DAG图中入度为0的点相当于起点,DAG图中出度为0的点相当于终点

#include <bits/stdc++.h>//有向图的强连通分量
using namespace std;
const int maxn=5050;
bool ins[maxn];
int n,m,cnt;
int head[maxn],be[maxn],in[maxn],out[maxn];//be是属于哪个连通分量  in,out是每个连通分量的入度和出度
int low[maxn],dfn[maxn],num,id;
stack <int> s;
struct node{int to,next;}e[maxn*2];

void add(int u,int v){ e[++cnt]=(node){v,head[u]};head[u]=cnt;}

void tarjan(int u){
	dfn[u]=low[u]=++num;//dfn访问序号,low是能走回到的最早的dfn
	ins[u]=1;
	s.push(u);//第一次访问节点时候入栈
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;
		if(!dfn[v]){//没访问过就递归访问
			tarjan(v);
			low[u]=min(low[u],low[v]);//获取孩子的最小的low值   
		}
		else if(ins[v]){//已经访问过且在栈中获取dfn号
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u]){//low[u]==dfn[u]时,则从栈中不断弹出节点,直到x出栈停止。弹出的节点就是同一个连通分量的
		int v;
		id++;
		do{//一定要先执行再判断
			v=s.top();s.pop();
			be[v]=id;//把这些弹出的点标记同一个id号(连通分量号)
			ins[v]=0;
		}while(v!=u);//直到是自己为止

	}
}

int main(){
	cin>>n;int v;//n为学校数量
	for(int i=1;i<=n;i++){
		while(cin>>v&&v)add(i,v);//表示接收i的v学校,以0结尾
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i]) tarjan(i);
	}
	for(int u=1;u<=n;u++)
		for(int i=head[u];i;i=e[i].next){
			int v=e[i].to;
			if(be[u]!=be[v]){//有重边,可以输出一下
				in[be[v]]++;out[be[u]]++;//统计入度和出度,来缩点
			}
		}
	if(id==1){//一共只要一个连通分量的话要特判
		cout<<1<<'\n';
		cout<<0<<'\n';
		return 0;
	}
	int ans1=0,ans2=0;
	//for(int i=1;i<=n;i++)cout<<i<<' '<<be[i]<<'\n';
	for(int i=1;i<=id;i++){
	//	cout<<i<<" in"<<' '<<in[i]<<" , "<<"out"<<' '<<out[i]<<'\n';
		if(!in[i]) ans1++;
		if(!out[i]) ans2++;
	}
	cout<<ans1<<'\n';
	cout<<max(ans1,ans2)<<'\n';	
}

        

        

        

缩点: 

        

         

思路:

有向图中的强连通分量中的所有权值一定要全部加上,所以缩点建出新的DAG图,然后转化成了每个点走一次求最大点权值和
设置dp[v]表示到v点的最大权值和。 dp[v]=max(dp[u])即可,也就是要先求dp[u]再求dp[v]。

因为要topo排序来dp,那么直接建一个新图是最方便的,不用在乎原图的多余的点和边!
        

	if(low[u]==dfn[u]){//low[u]==dfn[u]时,则从栈中不断弹出节点,直到x出栈停止。弹出的节点就是同一个连通分量的
		int v;	
		do{//一定要先执行再判断
			v=s.top();s.pop();
			be[v]=u;//把这些弹出的点标记同一个id号(连通分量号)
			ins[v]=0;
			if(u==v)break;//自己不要和自己加
			p[u]+=p[v];
		}while(v!=u);//直到是自己为止
	}

首先是缩点操作,要把该连通分量中点的权值加给连通分量点自己(类似无向图的桥点), 

        

for (int i=1;i<=m;i++)//遍历每个边
	{
		int u=be[e[i].from],v=be[e[i].to];//from是起点,to是终点
		if (u!=v)//不同的分量号点间进行建边,有重边也不影响topo结果
		{
			newe[++tt]=(node){v,hh[u],u};hh[u]=tt;in[v]++;//建新边过程,相当于add功能
		}
	}

然后是给新DAG图建边,以便后面topo。

        

完整代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=10000+15;
int n,m,tot,head[maxn],tt,hh[maxn],p[maxn];//p是每个点的权值,head和tot和e是原图的,hh和tt和newe是新图的
int num,low[maxn],dfn[maxn],ins[maxn],be[maxn];//be是每个所属的连通分量号
int in[maxn],dp[maxn];
stack<int>s;
struct node{int to,next,from;}e[maxn*10],newe[maxn*10];

void add(int u,int v){e[++tot]=(node){v,head[u],u};head[u]=tot;}

void tarjan(int u){
	dfn[u]=low[u]=++num;//dfn访问序号,low使能回溯到的最早的dfn
	ins[u]=1;
	s.push(u);//第一次访问节点时候入栈
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;
		if(!dfn[v]){//没访问过就递归访问
			tarjan(v);
			low[u]=min(low[u],low[v]);//获取孩子的最小的low值   
		}
		else if(ins[v]){//已经访问过且在栈中获取dfn号
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u]){//low[u]==dfn[u]时,则从栈中不断弹出节点,直到x出栈停止。弹出的节点就是同一个连通分量的
		int v;	
		do{//一定要先执行再判断
			v=s.top();s.pop();
			be[v]=u;//把这些弹出的点标记同一个id号(连通分量号)
			ins[v]=0;
			if(u==v)break;//自己不要和自己加
			p[u]+=p[v];
		}while(v!=u);//直到是自己为止
	}
}

int topo()
{
	queue <int> q;
	int tot=0;
	for (int i=1;i<=n;i++){
		if(be[i]==i&&!in[i]){//我们只是建了新边,没有建新点,导致要判断祖宗点
			q.push(i);
			dp[i]=p[i];
		}
	}
	while (!q.empty())//如果不去建新图虽然也能跑,不过是要跳过许多点,会非常麻烦
	{
		int u=q.front();q.pop();
		for (int i=hh[u];i;i=newe[i].next)
		{
			int v=newe[i].to;
			dp[v]=max(dp[v],dp[u]+p[v]);//要最大的起点嘛
			in[v]--;
			if (in[v]==0) q.push(v);
		}
	}
    int ans=0;
    for (int i=1;i<=n;i++)
    ans=max(ans,dp[i]);
    return ans;
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	scanf("%d",&p[i]);//权值
	for (int i=1;i<=m;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
	}
	for (int i=1;i<=n;i++)
		if (!dfn[i]) tarjan(i);

	for (int i=1;i<=m;i++)
	{
		int u=be[e[i].from],v=be[e[i].to];//from是起点,to是终点
		if (u!=v)//不同的分量号点间进行建边,有重边也不影响topo结果
		{
			newe[++tt]=(node){v,hh[u],u};hh[u]=tt;in[v]++;//建新边过程,相当于add功能
		}
	}
	printf("%d",topo());
}

  • 24
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
配送网络是合理安排配送路线的前提基础,对降低配送中心成本起着至关重要的作用。目前国内外对配送网络构建方面的研究还不够深入,本文利用图论和GIS技术对配送网络拓扑结构的构建方法进行研究,实现其自动化构建功能,建立物流配送系统所需的空间属性数据库,并应用于一个基于GIS的面向大规模城市物流配送的线路优化系统。 本文首先介绍了国内外网络拓扑构建和基于GIS物流配送系统的研究现状,提出现在GIS在配送线路优化应用中存在的问题,分析了物流配送系统所需的地理空间属性数据。然后,深入城市配送问题实践,针对城市物流配送和城市交通道路网的特点,研究道路网的矢量表达、网络拓扑结构的提取和构建、客户点与道路网的匹配等关键技术,提出一套将客户点添加进城市配送道路网络的规则,对地理信息和交通信息进行提取,构建了配送线路优化所需的包含客户资源信息和道路信息的拓扑网络。 在此基础上,结合烟草配送中心的实际情况,介绍了实际应用背景和问题模型算法,分析网络拓扑构建在配送中心线路优化系统中的应用。在MapInfo平台上实现算法与GIS的整合,对基于GIS的配送线路优化系统及网络拓扑构建模块的实现进行介绍。 本文的研究结果,成功应用在济南市烟草物流配送线路优化中,构建了覆盖辖区内28000个客户点的配送网络,取得了一定的经济效益。同时,本文研究同样适用于其它领域大规模城市物流配送,具有重要的实用价值。
复杂系统与复杂网络,社会计算科研工具书。 目 录 第一章 漫谈复杂性与复杂系统…………………………………………………………1 §1.1 熵…………………………………………………………………………………1 §1.2 计算机与信息……………………………………………………………………5 §1.3 算法复杂性………………………………………………………………………7 §1.4 非平衡统计物理学、耗散结构与协同学………………………………………8 §1.5 临界现象与自组织临界现象……………………………………………………11 §1.6 混沌………………………………………………………………………………20 §1.7 原胞自动机………………………………………………………………………24 §1.8 描述复杂性与统计复杂性………………………………………………………28 第二章 一些有关复杂网络研究的统计物理学方法…………………………………42 §2.1 连续相变的平均场理论…………………………………………………………42 §2.2 自组织临界现象的平均场理论…………………………………………………45 §2.3 流行病传播的平均场理论简介…………………………………………………49 §2.4 主方程……………………………………………………………………………51 §2.5 生成函数…………………………………………………………………………55 §2.6 率方程……………………………………………………………………………56 第三章 博弈论及演化网络博弈…………………………………………………………59 §3.1 基本概念…………………………………………………………………………59 §3.2 完全信息静态博弈与纳什均衡…………………………………………………63 §3.3 完全信息动态博弈与子博弈精炼纳什均衡……………………………………66 §3.4 不完全信息静态博弈与贝叶斯纳什均衡………………………………………68 §3.5 不完全信息动态博弈与精炼贝叶斯纳什均衡…………………………………70 §3.6 合作博弈…………………………………………………………………………73 §3.7 演化网络博弈……………………………………………………………………74 §3.8 城市公交网络网络操纵者博弈模型…………………………………………82 第四章 数理统计简介………………………………………………………………………87 §4.1 一些基本概念……………………………………………………………………87 §4.2 统计假设及其检验………………………………………………………………90 §4.3 一元线性回归……………………………………………………………………92 §4.4 回归的一些问题…………………………………………………………………95 §4.5 漫谈数据的采集与处理…………………………………………………………102 第五章 图论简介……………………………………………………………………………106 §5.1 一些基本概念……………………………………………………………………109 §5.2 的连通性………………………………………………………………………112 §5.3 树………………………………………………………………………………114 §5.4 最短道路问题……………………………………………………………………115 §5.5 的矩阵描述……………………………………………………………………116 §5.6 有向……………………………………………………………………………118 §5.7 二分……………………………………………………………………………119 §5.8 网络流……………………………………………………………………………120 第六章 复杂网络的统计描述……………………………………………………………124 §6.1 平均距离、谐平均距离、效率与脆弱性………………………………………124 §6.2 集群系数、圈系数、富人集团系数、集团度………………………………125 §6.3 度、度分布、度相关性………………………………………………………128 §6.4 边权网及边权的一些统计性质………………………………………………129 §6.5 二分的二分度………………………………………………………………129 §6.6 中心度与中心化………………………………………………………………130 §6.7 谱分析…………………………………………………………………………133 §6.8 模体……………………………………………………………………………134 §6.9 群落、派系与层次……………………………………………………………135 §6.10 度分布熵、目标熵以及不同的网络信息熵…………………………………137 §6.11 多标度分形的分数维谱………………………………………………………139 §6.12 漫谈复杂网络的统计描述……………………………………………………141 第七章 一些网络演化模型……………………………………………………………143 §7.1 ER随机网模型…………………………………………………………………143 §7.2 WS小世界网模型………………………………………………………………146 §7.3 BA无标度网模型………………………………………………………………151 §7.4 BA无标度网模型的主方程解…………………………………………………154 §7.5 BA无标度网模型的率方程解…………………………………………………156 §7.6 部分优选、部分随机选择模型………………………………………………157 §7.7 局域世界模型…………………………………………………………………159 §7.8 赋权演化网络的BBV模型……………………………………………………161 §7.9 可调集群系数的HK模型及其改进模型………………………………………163 §7.10 JGN社会网络模型……………………………………………………………165 §7.11 自组织共演化模型……………………………………………………………167 §7.12 其它运用统计物理学方法的模型研究………………………………………176 第八章 复杂网络上的物理传输过程…………………………………………………183 §8.1 流行病传播的基本模型…………………………………………………183 §8.2 复杂网络上的流行病传播………………………………………………186 §8.3 复杂网络上的舆论传播…………………………………………………190 §8.4 群落网结构对流行病传播的影响………………………………………194 §8.5 动态群落网上的流行病传播……………………………………………198 §8.6 因特网上的包裹传递……………………………………………………202 §8.7 因特网上交通堵塞的控制………………………………………………204 §8.8 交通数据的去趋势涨落分析……………………………………………210 §8.9 复杂网络上的粒子输运…………………………………………………213 §8.10 粒子输运的平均场方法…………………………………………………215 §8.11 加权复杂网络上的粒子输运……………………………………………217 §8.12 简单网络上能量输运……………………………………………………219 §8.13 复杂网络上能量输运……………………………………………………222 第九章 一些生命网络的研究……………………………………………………………231 §9.1 大脑功能网络……………………………………………………………………231 §9.2 两态小动物群体网络……………………………………………………………234 §9.3 生物分子网络……………………………………………………………………241 第十章 合作网络与合作-竞争网络……………………………………………………249 §10.1 简介……………………………………………………………………………249 §10.2 比较早期的合作网实证研究…………………………………………………251 §10.3 合作网的项目大小分布和项目度分布………………………………………253 §10.4 合作网的同类性与项目度分布的相关性……………………………………259 §10.5 二分投影的资源分配方法…………………………………………………263 §10.6 近期关于合作网络的实证研究………………………………………………265 §10.7 关于合作-竞争网络的研究…………………………………………………268 第十一章 网络动力学的一些探索………………………………………………………274 §11.1 布尔网络、信息距离以及一些复杂网络的非线性动力学…………………274 §11.2 最小作用量原理与网络形态的自然选择……………………………………277 §11.3 的动力学谱分析……………………………………………………………281

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值