【算法每日一练]-图论(保姆级教程篇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

        
思路:

先求解边双连通分量,然后缩点,然后通过加边再把新图变成双连通图。

加边原理是这样的:
先统计叶节点个数为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值,就是该点能回到的最小dfnlow[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]]++;
//有重边也要记录。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';}	
}

        

        

POJ2553:图的底部

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

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

思路:

你只需要输出出度为0的连通分量中的所有点编号即可。

DAG图的出度为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; }
}

不同于无向图,有向图的连通分量号我们用一个be数组存起来 

然后对所有边进行判断是不是连接着两个分量,然后对新树中的边统计出度,输出出度为0的连通分量中的点

#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是能走回到的最早的dfnins[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的点相当于起点

#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是能走回到的最早的dfnins[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排序求一边就行了。完了!
        

	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使能回溯到的最早的dfnins[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());
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/213774.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【华为数据之道学习笔记】1-2华为数字化转型与数据治理

传统企业通过制造先进的机器来提升生产效率&#xff0c;但是未来&#xff0c;如何结构性地提升服务和运营效率&#xff0c;如何用更低的成本获取更好的产品&#xff0c;成了时代性的问题。数字化转型归根结底就是要解决企业的两大问题&#xff1a;成本和效率&#xff0c;并围绕…

go写文件后出现大量NUL字符问题记录

目录 背景 看看修改前 修改后 原因 背景 写文件完成后发现&#xff1a; size明显也和正常的不相等。 看看修改前 buf : make([]byte, 64) buffer : bytes.NewBuffer(buf)// ...其它逻辑使得buffer有值// 打开即将要写入的文件&#xff0c;不存在则创建 f, err : os.Open…

solidity案例详解(五)能源电力竞拍合约

使用智能合约对电力公司和用户拍拍进行一个管理与上链&#xff0c;确保安全性&#xff0c;合约完整代码私信 a)现有系统架构和功能&#xff0c;服务提供方是谁&#xff0c;用户是谁&#xff1b; 系统架构&#xff1a; 电力拍卖系统&#xff0c;由能源公司部署。 服务提供方&a…

IntelliJ IDEA创建一个Maven项目

在IDEA中创建Maven项目&#xff0c;前提是已经安装配置好Maven环境 。 本文主要使用的是IntelliJ IDEA 2022.2.1 (Community Edition) 1.创建一个新project:File>Project 2.修改Maven配置&#xff1a;File>Settings>搜索maven 创建好的工程如下&#xff1a; src/main…

记录 | ubuntu监控cpu频率、温度等

ubuntu监控cpu频率、温度等 采用 i7z 进行监控&#xff0c;先安装&#xff1a; sudo apt install i7z -ysudo i7z

STM32单片机项目实例:基于TouchGFX的智能手表设计(3)嵌入式程序任务调度的设计

STM32单片机项目实例&#xff1a;基于TouchGFX的智能手表设计&#xff08;3&#xff09;嵌入式程序任务调度的设计 目录 一、嵌入式程序设计 1.1轮询 1.2 前后台&#xff08;中断轮询&#xff09; 1.3 事件驱动与消息 1.3.1 事件驱动的概念 1.4 定时器触发事件驱动型的任…

如何利用人工智能+物联网技术实现自动化设备生产

随着科技的发展与行业竞争的日益激烈&#xff0c;制造业也逐渐走向智能化发展。制造业的改革是利用物联网技术和自动化设备&#xff0c;实现生产线的智能化和自适应生产&#xff0c;优化生产流程&#xff0c;提高生产效率和质量&#xff0c;为企业创造更大的价值。 方案概述 智…

spring boot定时器实现定时同步数据

文章目录 目录 文章目录 前言 一、依赖和目录结构 二、使用步骤 2.1 两个数据源的不同引用配置 2.2 对应的mapper 2.3 定时任务处理 总结 前言 一、依赖和目录结构 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifa…

自动化定时发送天气提醒邮件

&#x1f388; 博主&#xff1a;一只程序猿子 &#x1f388; 博客主页&#xff1a;一只程序猿子 博客主页 &#x1f388; 个人介绍&#xff1a;爱好(bushi)编程&#xff01; &#x1f388; 创作不易&#xff1a;如喜欢麻烦您点个&#x1f44d;或者点个⭐&#xff01; &#x1f…

redis中缓存雪崩,缓存穿透,缓存击穿等

缓存雪崩 由于原有缓存失效&#xff08;或者数据未加载到缓存中&#xff09;&#xff0c;新缓存未到期间&#xff08;缓存正常从Redis中获取&#xff0c;如下图&#xff09;所有原本应该访问缓存的请求都去查询数据库了&#xff0c;而对数据库CPU和内存造成巨大压力&#xff0c…

STM32单片机项目实例:基于TouchGFX的智能手表设计(2)UI交互逻辑的设计

STM32单片机项目实例&#xff1a;基于TouchGFX的智能手表设计&#xff08;2&#xff09;UI交互逻辑的设计 目录 一、UI交互逻辑的设计 1.1 硬件平台的资源 1.2 界面切换功能 ​​​​​​​1.3 表盘界面 1.4 运动界面 ​​​​​​​1.6 设置界面 ​​​​​​​1.7 应…

vm net 方式 静态ip配置访问主机IP和外网

1、win 11 安装vm&#xff0c;镜像文件 F:\software\VMwork\CentOS-7-x86_64-Everything-1804.iso 2、配置网络 net 方式 3、右击网络--》属性---》更改适配器设置--》vmnet8 属性、这里不做配置会出现主机ping通访问不通的情况&#xff0c;&#xff08;访问不通&#xff0c;…

v4l2接收流程

内核media驱动目录结构 目录media/driver,子目录说明如下&#xff0c;主要列举本文中使用到的目录 目录功能I2C摄像头&#xff0c;解串器&#xff08;max9296/9295等&#xff09;platform控制器的驱动&#xff0c;例如mipi控制等v4l2_coreioctl 入口等media\common\videobuf2…

在python中安装库,会有conda安装,也会有pip安装,conda与pip的区别是什么?

文章目录 一、Conda是什么&#xff1f;二、pip是什么&#xff1f;三、pip与conda的区别&#xff1a;总结 一、Conda是什么&#xff1f; Conda是一个开源的包管理系统&#xff0c;它是Anaconda公司为Python和其他编程语言开发的。它主要用于数据科学和机器学习领域&#xff0c;…

菜鸟学习日记(python)——迭代器与生成器

迭代器 迭代是 Python 最强大的功能之一&#xff0c;是访问集合元素的一种方式。 迭代器是一个可以记住遍历的位置的对象。 迭代器对象从集合的第一个元素开始访问&#xff0c;直到所有的元素被访问完结束。迭代器只能往前不会后退。 迭代器有两个基本的方法&#xff1a;it…

玩转大数据6:实时数据处理与流式计算

引言 在当今的数字化时代&#xff0c;数据正在成为一种新的资源&#xff0c;其价值随着时间的推移而不断增长。因此&#xff0c;实时数据处理和流式计算变得越来越重要。它们在许多领域都有广泛的应用&#xff0c;包括金融、医疗、交通、能源等。本文将探讨实时数据处理和流式…

Nginx的性能优化、安全以及防盗链配置

目录 一、nginx的日志分割 二、nginx性能优化之启用epoll模型 三、nginx性能优化之设置worker进程数并与cpu进行绑核 四、nginx性能优化之调整worker的最大打开文件数和最大处理连接请求数量 五、nginx性能优化之启用gzip压缩&#xff0c;提高传输&#xff0c;减少带宽 六…

从零到一学习RocketMQ

RocketMQ 是一款功能强大的分布式消息系统&#xff0c;广泛应用于多个领域&#xff0c;包括异步通信解耦、企业解决方案、金融支付、电信、电子商务、快递物流、广告营销、社交、即时通信、移动应用、手游、视频、物联网、车联网等。 RocketMQ 源码地址&#xff1a;https://gi…

leetcode系列:反转链表的形象表示

反转链表是一道比较简单的题&#xff0c;主要考察的是对链表数据结构的理解和双指针应用&#xff0c;比较容易出错的地方是指针的移动顺序。在练习的过程中想到了一个比较形象的表示方法&#xff0c;于是记录下来。 # Definition for singly-linked list. # class ListNode: #…

Vue Computed

小满&#xff0c;我的神&#xff01; 视频链接 // 只读 const plusOne computed(() > count.value 1) // 可读可写 const plusOne computed({get: () > count.value 1,set: (val) > {count.value val - 1} }, { // 用于调试onTrack(e) {debugger},onTrigger(e) …