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 个交叉路口组成。
最近,比特镇获得了一场铁人两项锦标赛的主办权。这场比赛共有两段赛程:选手先完成一段长跑赛程,然后骑自行车完成第二段赛程。
比赛的路线要按照如下方法规划:
- 先选择三个两两互不相同的路口 s、c 和 f,分别作为比赛的起点、切换点(运动员在长跑到达这个点后,骑自行车前往终点)、终点。
- 选择一条从 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;
}