洛谷-【图论2-4】连通性问题1

P1656 炸铁路

题目描述

A 国派出将军 uim,对 B 国进行战略性措施,以解救涂炭的生灵。

B 国有 n 个城市,这些城市以铁路相连。任意两个城市都可以通过铁路直接或者间接到达。

uim 发现有些铁路被毁坏之后,某两个城市无法互相通过铁路到达。这样的铁路就被称为 key road。

uim 为了尽快使该国的物流系统瘫痪,希望炸毁铁路,以达到存在某两个城市无法互相通过铁路到达的效果。

然而,只有一发炮弹(A 国国会不给钱了)。所以,他能轰炸哪一条铁路呢?

输入格式

第一行 n,m (1≤n≤150,1≤m≤5000),分别表示有 n 个城市,总共 m 条铁路。

以下 m 行,每行两个整数 a,b,表示城市 a 和城市 b 之间有铁路直接连接。

保证不存在重边(a,b 和 b,a 也视为重边)。

输出格式

输出有若干行。

每行包含两个数字 a,b,其中 a<b,表示 ⟨a,b⟩ 是 key road。

请注意:输出时,所有的数对 ⟨a,b⟩ 必须按照 a 从小到大排序输出;如果 a 相同,则根据 b 从小到大排序。

输入输出样例

输入 #1复制

复制代码
6 6
1 2
2 3
2 4
3 5
4 5
5 6

输出 #1复制

复制代码
1 2
5 6

实现代码:

cpp 复制代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=10010;
int n,m,x,y,index_,dfn[maxn],low[maxn],ans,a;
vector<int>G[maxn];
struct Edge{int from,to;}edge[maxn];
bool cmp(const Edge a,const Edge b){if(a.from!=b.from)return a.from<b.from;return a.to<b.to;}
inline void add_edge(int x,int y){edge[ans].from=min(x,y);edge[ans].to=max(x,y);ans++;}
void dfs(int cur,int fa)
{
    int child;
    dfn[cur]=++index_;
    low[cur]=dfn[cur];
    bool vis=false; 
    for(int i=0;i<G[cur].size();i++)
    {
        child=G[cur][i];
        if(dfn[child])
		{
			if(child==fa&&!vis)vis=true;
			else low[cur]=min(low[cur],dfn[child]);
		}
        if(!dfn[child])
        {
            dfs(child,cur);
            if(dfn[cur]<low[child])add_edge(cur,child);
            low[cur]=min(low[cur],low[child]);
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++)scanf("%d%d",&x,&y),G[x].push_back(y),G[y].push_back(x);
    for(int i=1;i<=n;i++)if(!dfn[i])dfs(i,i);
    sort(edge,edge+ans,cmp);
    for(int i=0;i<ans;i++)printf("%d %d\n",edge[i].from,edge[i].to);
    return 0;
}

P2860 [USACO06JAN] Redundant Paths G

题目描述

贝西和其他牛需要在 F(1≤F≤5,000) 个牧场间移动(编号为 1 到 F)。他们厌倦了走某些特定的路径,因而想要修建一些新路,使得在任意一对牧场之间总有至少两条路线可供选择。目前在每对牧场之间至少有一条路径。当然,他们只能在官方道路上移动。

当前有 R(F−1≤R≤10,000) 条道路,每条道路连接两个不同的牧场。请你确定必须修建的最小道路数量(每条新道路也要连接两个不同的牧场),使得在任意一对牧场之间至少有两条路线。两条路线只要没有使用同一条道路就被视为合法的(即使经过了相同的牧场)。

在同一对牧场之间可能已有多条路径。修建的新路可以与某条现有道路连接一对相同的牧场。

输入格式

第 1 行:两个用空格分隔的整数:F 和 R。

第 2 行到第 R+1 行:每行包含两个用空格分隔的整数,表示某条路径连接的两个牧场。

输出格式

一行一个整数,表示必须修建的新路径数量。

输入输出样例

输入 #1复制

复制代码
7 7
1 2
2 3
3 4
2 5
4 5
5 6
5 7

输出 #1复制

复制代码
2

说明/提示

样例解释:

初始路径如下:

可以在 1 和 6 , 4 和 7 间修建新路。

一些例子:

  • 1−2:1→2 或 1→6→5→2
  • 1−4:1→2→3→4 或 1→6→5→4
  • 3−7:3→4→7 或 3→2→5→7

可以发现,每对牧场之间都有至少两条路径。

其他道路修建方式也可能解决问题(例如从 6 到 7 的道路),但是添加两条是最少的。

(由 ChatGPT 4o 翻译并人工整改)

实现代码:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N=5e3+5,M=1e4+5;
int n,m,vis[M<<1],du[N],ans;
int cnt=1,head[N],u[M],v[M];
int now,top,col,dfn[N],low[N],sta[N],color[N];
struct edge{int next,to;}e[M<<1];

inline void add(int u,int v)
{
	cnt++;
	e[cnt].next=head[u];
	e[cnt].to=v;
	head[u]=cnt;
	cnt++;
	e[cnt].next=head[v];
	e[cnt].to=u;
	head[v]=cnt;
}

inline void tarjan(int u)
{
	dfn[u]=low[u]=++now;	
	sta[++top]=u;
	for (register int i=head[u]; i; i=e[i].next)
	if (!vis[i])
	{
	vis[i]=vis[i^1]=1;
		if (!dfn[e[i].to])
		{
			tarjan(e[i].to);	
			low[u]=min(low[u],low[e[i].to]);
		}
		else low[u]=min(low[u],dfn[e[i].to]);
	}
	if (low[u]==dfn[u])
	{
		color[u]=++col;	
		while (sta[top]!=u) color[sta[top]]=col,top--;
		top--;
	}
}

int main(){
memset(head,0,sizeof(head));
memset(dfn,0,sizeof(head));
	scanf("%d%d",&n,&m);
	for (register int i=1; i<=m; ++i) scanf("%d%d",&u[i],&v[i]),add(u[i],v[i]);
	for (register int i=1; i<=n; ++i) if (!dfn[i]) tarjan(i);
	for (register int i=1; i<=m; ++i) if (color[u[i]]!=color[v[i]]) du[color[u[i]]]++,du[color[v[i]]]++;
	for (register int i=1; i<=col; ++i) if (du[i]==1) ans++;
printf("%d\n",ans+1>>1);
return 0;	
}

P3388 【模板】割点(割顶)

题目背景

割点

题目描述

给出一个 n 个点,m 条边的无向图,求图的割点。

输入格式

第一行输入两个正整数 n,m。

下面 m 行每行输入两个正整数 x,y 表示 x 到 y 有一条边。

输出格式

第一行输出割点个数。

第二行按照节点编号从小到大输出节点,用空格隔开。

输入输出样例

输入 #1复制

复制代码
6 7
1 2
1 3
1 4
2 5
3 5
4 5
5 6

输出 #1复制

复制代码
1
5

说明/提示

对于全部数据,1≤n≤2×104,1≤m≤1×105。

点的编号均大于 0 小于等于 n。

Tarjan 图不一定连通。

实现代码:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 1e5 + 5;
int n, m, R;
int dn, dfn[N], low[N], cnt, buc[N];
vector<int> e[N];
void dfs(int id) {
  dfn[id] = low[id] = ++dn; 
  int son = 0;
  for(int it : e[id]) {
    if(!dfn[it]) {
      son++, dfs(it), low[id] = min(low[id], low[it]);
      if(low[it] >= dfn[id] && id != R) cnt += !buc[id], buc[id] = 1;
    }
    else low[id] = min(low[id], dfn[it]);
  }
  if(son >= 2 && id == R) cnt += !buc[id], buc[id] = 1;
}
int main() {
  cin >> n >> m;
  for(int i = 1; i <= m; i++) {
    int u, v;
    cin >> u >> v;
    e[u].push_back(v), e[v].push_back(u);
  }
  for(int i = 1; i <= n; i++) if(!dfn[i]) R = i, dfs(i);
  cout << cnt << endl;
  for(int i = 1; i <= n; i++) if(buc[i]) cout << i << " ";
  return 0;
}

P4630 [APIO2018] 铁人两项

题目描述

比特镇的路网由 m 条双向道路连接的 n 个交叉路口组成。

最近,比特镇获得了一场铁人两项锦标赛的主办权。这场比赛共有两段赛程:选手先完成一段长跑赛程,然后骑自行车完成第二段赛程。

比赛的路线要按照如下方法规划:

  1. 先选择三个两两互不相同的路口 s、c 和 f,分别作为比赛的起点、切换点(运动员在长跑到达这个点后,骑自行车前往终点)、终点。
  2. 选择一条从 s 出发,经过 c 最终到达 f 的路径。考虑到安全因素,选择的路径经过同一个点至多一次。

在规划路径之前,镇长想请你帮忙计算,总共有多少种不同的选取 s、c 和 f 的方案,使得在第 2 步中至少能设计出一条满足要求的路径。

输入格式

第一行包含两个整数 n 和 m,分别表示交叉路口和双向道路的数量。

接下来 m 行,每行两个整数 vi​,ui​。表示存在一条双向道路连接交叉路口 vi​,ui​(1≤vi​,ui​≤n,vi​=ui​)。

保证任意两个交叉路口之间,至多被一条双向道路直接连接。

输出格式

输出一行,包括一个整数,表示能满足要求的不同的选取 s、c 和 f 的方案数。

输入输出样例

输入 #1复制

复制代码
4 3
1 2
2 3
3 4

输出 #1复制

复制代码
8

输入 #2复制

复制代码
4 4
1 2
2 3
3 4
4 2

输出 #2复制

复制代码
14

说明/提示

提示

在第一个样例中,有以下 8 种不同的选择 (s,c,f) 的方案:

  • (1,2,3),(1,2,4),(1,3,4),(2,3,4),(3,2,1);
  • (4,2,1),(4,3,1),(4,3,2)。

在第二个样例中,有以下 14 种不同的选择 (s,c,f) 的方案:

  • (1,2,3),(1,2,4),(1,3,4),(1,4,3),(2,3,4);
  • (2,4,3),(3,2,1),(3,2,4),(3,4,1),(3,4,2);
  • (4,2,1),(4,2,3),(4,3,1),(4,3,2)。

子任务

  • Subtask 1(points: 5):n≤10,m≤100。
  • Subtask 2(points: 11):n≤50,m≤100。
  • Subtask 3(points: 8):n≤100000,每个交叉路口至多作为两条双向道路的端点。
  • Subtask 4(points: 10):n≤1000,在路网中不存在环(存在环是指存在一个长度为 k(k≥3)的交叉路口序列 v1,v2,...,vk,序列中的路口编号两两不同,且对于 i 从 1 到 k−1,有一条双向道路直接连接路口 vi 和 vi+1,且有一条双向道路直接连接路口 vk 和 v1)。
  • Subtask 5(points: 13):n≤100000,在路网中不存在环。
  • Subtask 6(points: 15):n≤1000,对于每个交叉路口,至多被一个环包含。
  • Subtask 7(points: 20):n≤100000,对于每个交叉路口,至多被一个环包含。
  • Subtask 8(points: 8):n≤1000,m≤2000。
  • Subtask 9(points: 10):n≤100000,m≤200000。

实现代码:

cpp 复制代码
#include <cstdio>

typedef long long LL;

const int MAXN = 2e5 + 5, MAXM = 2e6 + 5;

template<typename _T>
void read( _T &x )
{
	x = 0;char s = getchar();int f = 1;
	while( s > '9' || s < '0' ){if( s == '-' ) f = -1; s = getchar();}
	while( s >= '0' && s <= '9' ){x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar();}
	x *= f;
}

template<typename _T>
void write( _T x )
{
	if( x < 0 ){ putchar( '-' ); x = ( ~ x ) + 1; }
	if( 9 < x ){ write( x / 10 ); }
	putchar( x % 10 + '0' );
}

template<typename _T>
_T MIN( const _T a, const _T b )
{
	return a < b ? a : b;
}

struct GRAPH
{
	struct edge
	{
		int to, nxt;
	}Graph[MAXM << 1];
	
	int head[MAXN] = {}, cnt;
	
	GRAPH() { cnt = 0; }
	
	void addEdge( const int from, const int to )
	{
		Graph[++ cnt].to = to, Graph[cnt].nxt = head[from];
		head[from] = cnt;
	}
	
	void addE( const int from, const int to )
	{
		addEdge( from, to ), addEdge( to, from );
	}
	
	void nxt( int &ptr ) const { ptr = Graph[ptr].nxt; }
	edge& operator [] ( const int indx ) { return Graph[indx]; }
}G, T;

int stk[MAXN];
int siz[MAXN], w[MAXN];
int DFN[MAXN], LOW[MAXN];
LL ans;
int N, M, cnt, ID, top, tot, subn;

void Tarjan( const int u, const int fa )
{
	subn ++;
	DFN[u] = LOW[u] = ++ ID;
	w[stk[++ top] = u] = -1;
	for( int i = G.head[u], v ; i ; G.nxt( i ) )
		if( ( v = G[i].to ) ^ fa )
		{
			if( ! DFN[v] )
			{
				Tarjan( v, u );
				LOW[u] = MIN( LOW[u], LOW[v] );
				if( LOW[v] >= DFN[u] )
				{
					T.addE( ++ tot, u ), w[tot] ++;
					do T.addE( tot, stk[top] ), w[tot] ++;
					while( stk[top --] ^ v );
				}
			}
			else LOW[u] = MIN( LOW[u], DFN[v] );
		}
}

void DFS( const int u, const int fa )
{
	siz[u] = u <= N;
	for( int i = T.head[u], v ; i ; T.nxt( i ) )
		if( ( v = T[i].to ) ^ fa )
		{
			DFS( v, u ); 
			ans += 2ll * siz[u] * siz[v] * w[u];
			siz[u] += siz[v];
		}
	ans += 2ll * siz[u] * ( subn - siz[u] ) * w[u];
}

int main()
{
	read( N ), read( M ), tot = N;
	for( int i = 1, a, b ; i <= M ; i ++ )
		read( a ), read( b ), G.addE( a, b );
	for( int i = 1 ; i <= N ; i ++ )
		if( ! DFN[i] )
		{
			subn = 0;
			Tarjan( i, 0 );
			DFS( i, 0 );
		}
	write( ans ), putchar( '\n' );
	return 0;
}
相关推荐
RSCompany5 小时前
Frida 17 以后 Python API 跑旧版 JS 报 Java is not defined ?一行 import 直接恢复 Frida 16 体验
开发语言·python·逆向·hook·frida·android逆向·frida17
快乐的哈士奇5 小时前
对话框打字机效果:Vur + Java/Python 实现
java·开发语言·python
我先去打把游戏先5 小时前
Ubuntu虚拟机(服务器版本)Git安装教程(附常用命令)——从零开始掌握版本控制
服务器·c语言·c++·git·嵌入式硬件·物联网·ubuntu
周末也要写八哥5 小时前
算法实例分析:使数组相等的最小开销
算法
ch.ju5 小时前
Java程序设计(第3版)第四章——类的组成
java·开发语言
我命由我123455 小时前
PHP - PHP 基本随机数生成函数
开发语言·ide·后端·java-ee·php·intellij-idea·intellij idea
博.闻广见5 小时前
AI_Python基础-4.标准库与IO
开发语言·python
吃好睡好便好5 小时前
在Matlab中绘制质点运动轨迹图
开发语言·学习·算法·matlab·信息可视化
艾莉丝努力练剑5 小时前
【Linux网络】Linux 网络编程:HTTP(四)从手写服务器到生产级 Nginx 与 cpp-httplib 实战
linux·运维·服务器·网络·c++·nginx·http