图论算法

说明

弥补一下图论方面的欠缺

二维图上的并查集

#include <iostream>
#include <cstring>

using std::cout;

struct Dsu {
  	const int size;
  	int f[1000];
  	int numOfBlock[1000];
  	int numOfChest[1000];
  	
  	Dsu(int size)
  		:size(size)
  		{
  			memset(f,-1,sizeof(f));	
  			memset(numOfBlock,0,sizeof(numOfBlock));	
  			memset(numOfChest,0,sizeof(numOfChest));	
		}
	int find(int a,int b) {
		return find(id(a,b));
	}
	int find(int n) {
	  	if (f[n] == -1)
	  	  return n;
	  	else
	  	  return f[n] = find(f[n]);
	}
	int id(int a,int b) {
		return a * size + b;
	}
	bool is_same(int a,int b) {
	  	return find(a) == find(b); 
	}
	void uni(int a,int b,int c,int d) {
		uni(id(a,b),id(c,d));
	}
	void mergeInfo(int a,int b) {
		numOfChest[find(b)] += numOfChest[find(a)];
		numOfBlock[find(b)] += numOfBlock[find(a)];
		numOfChest[find(a)] = 0;
		numOfBlock[find(a)] = 0;
	}
	void uni(int a,int b) {
	  	if (!is_same(a,b)) {
	  		mergeInfo(a,b);
			f[find(a)] = find(b);
		}
	}
};
char g[100][100] = {
"   1      ",
"  21      ",
"1111 11111",
"      1   ",
"      12  ",
"      1   ",
"   2  1   ",
"      1   ",
" 2    1   ",
"      1   ",
};
void checkAndUni(Dsu & dsu,int a,int b,int c,int d) {
	if (a < 0 or b < 0 or c < 0 or d < 0) {
		return;
	}
	else if (a >= dsu.size or b >= dsu.size or c >= dsu.size or d >= dsu.size) {
		return;
	}
	else if (g[a][b] == '1' or g[c][d] == '1'){
		return;
	}
	else {
		dsu.uni(a,b,c,d);
	}
}
int main() {
	const int size = 10;
	Dsu dsu(10);
	
	for (int i = 0;i < size;++i) {
		for (int k = 0;k < size;++k) {
			if (g[i][k] != '1') {
				auto id = dsu.id(i,k);
				if (g[i][k] == '2') {
					dsu.numOfChest[id] = 1;
				}   
				else {
					dsu.numOfBlock[id] = 1;
				}
			} 
		}
	}
	
	for (int i = 0;i < size;++i) {
		for (int k = 0;k < size;++k) {
			if (g[i][k] != '1') {
				checkAndUni(dsu,i,k,i - 1,k);
				checkAndUni(dsu,i,k,i + 1,k);
				checkAndUni(dsu,i,k,i,k - 1);
				checkAndUni(dsu,i,k,i,k + 1);
			} 
		}
	}
	for (int i = 0;i < size;++i) {
		for (int k = 0;k < size;++k) {
			cout << dsu.find(i,k) << " "; 
		}
		cout << "\n";
	}
	
	int a = 1,b = 1;
	auto id = dsu.find(dsu.id(a,b));
	cout << "(" << a << "," << b << ")" << " = " << dsu.numOfBlock[id] << " " << dsu.numOfChest[id] << "\n"; 
	return 0;
}

基环树

就是树 + 一个边,
有向的分为内向树和外向树

找环 : 拓扑排序、tarjan、一个简化版本的dfs
一般就是把环和非环拆开看,或者把一个边断开,强行维护

最大流

最大流最小割定理

下列条件等价
f是G的一个最大流
残留网络\(G_f\)不包含增广路径
对G的某个割(S,T)有,\(f = c(S,T)\)

基本思路

把图等效成单源单汇的图
最大流 = 最小割,最小割就是割掉若干条边
把图分成分别包含源和汇的两部分,的边的流量和
思路非常简单,首先对于每条边,增加一条流量为0的反向边
然后不断的找增广路,对于每条找到的增光路,增加反向边
然后不停按照这个思路贪心就行了

不带任何优化的算法 : Edmonds_Karp

\(O(VE^2)\)

#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
namespace Forward_Star
  {
	struct node
	  {
	  	ll to,net,val;
		node(ll to = 0,ll net = 0,ll val = 0)
		 :to(to),net(net),val(val)
		   {}
	   };
	vector<node> edge(2);
	vector<ll> head;
	void init(ll n,ll m)
	  {
	  	edge.resize(2);
	  	head = vector<ll>(n + 1);
	  }
	void add_edge(ll a,ll b,ll c)
	  {
	  	edge.emplace_back(b,head[a],c);
		head[a] = edge.size() - 1;
	  }
  }
namespace Edmonds_Karp
  {
	vector<ll> pre;
	void init(ll n,ll m)
	  {
	  	pre.resize(n + 1);
	  }
	ll bfs(ll n,ll star,ll fini)
	  {
	    vector<ll> vis(n + 1),dis(n + 1);
		queue<ll> q;
		q.push(star);
		vis[star] = true;
		dis[star] = numeric_limits<ll>::max();
		while(!q.empty())
		  {
		  	auto x = q.front();q.pop();
			for(ll i = Forward_Star::head[x];i;i = Forward_Star::edge[i].net) 
			  {
			    auto it = Forward_Star::edge[i].to;
				if (Forward_Star::edge[i].val == 0) 
				  continue;  //我们只关心剩余流量>0的边 
				if (vis[it] == 1) 
				  continue;  //这一条增广路没有访问过 
				dis[it] = min(dis[x],Forward_Star::edge[i].val);
				pre[it] = i;  //记录前驱,方便修改边权 
				q.push(it);
				vis[it] = 1;
				if (it == fini) 
				  return dis[fini];  //找到了一条增广路 
			}
		}
		return 0;
	}
	void update(ll star,ll fini,ll add)
	  {
	  	for (auto x = fini;x != star;)
	  	  {
	  	  	auto it = pre[x];
	  	  	Forward_Star::edge[it].val -= add;
			Forward_Star::edge[it ^ 1].val += add;
			x = Forward_Star::edge[it ^ 1].to;
			}
	  }
   } 

int main() 
  {
  	ll n,m,star,fini;
  	cin >> n >> m >> star >> fini;
  	Forward_Star::init(n,m);
  	Edmonds_Karp::init(n,m);
	for (ll i = 1;i <= m;i++)
	  {
	  	ll a,b,c;
	  	cin >> a >> b >> c;
	  	Forward_Star::add_edge(a,b,c);
	  	Forward_Star::add_edge(b,a,0);
	  }
	ll ret = 0;
	for (auto add = Edmonds_Karp::bfs(n,star,fini);
			  add;
	          add = Edmonds_Karp::bfs(n,star,fini)
		)
	  {
	  	ret += add;
		Edmonds_Karp::update(star,fini,add);
	  }
	cout << ret << "\n";
	return 0;
}

压入与重标记算法

重标记与前移算法

二分图匹配

完全匹配

指匹配数为\(min(n,m)\)的匹配

hall定理---用来判断二分图是不是有完全匹配

设n为左部,m为右部,且n < m,如果对于任意k from 1 to n,都有
左部任意k个点的邻居不少于k个
那么该二分图完美匹配

Hopcroft-Karp算法

复杂度\(O(m\sqrt{n})\)
该算法主要是对匈牙利算法的优化,在寻找增广路径的时候同时寻找多条不相交的增广路径,形成极大增广路径集,然后对极大增广路径集进行增广。在寻找增广路径集的每个阶段,找到的增广路径集都具有相同的长度,且随着算法的进行,增广路径的长度不断的扩大。可以证明,最多增广n^0.5次就可以得到最大匹配。

KM算法

复杂度O(VE)
非常好理解,能增广就增广
贪心可以由hall定理证明正确性
最后x_match存的就是x的匹配点

#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
namespace Km
  {
	vector<vector<ll>> g;
	void init(ll n,ll m)
	  {
	  	g = vector<vector<ll>>(n + 1);
	  }
	void add_edge(ll a,ll b)
	  {
	  	g[a].emplace_back(b);
	  }
	bool dfs(ll now,vector<ll> & x_match,vector<ll> & y_match,vector<ll> & vis)
	  {
	  	for (auto it : g[now])
	  	  if (vis[it] == 0)
	  	    {
	  	    	vis[it] = 1;
	  	    	if (y_match[it] == -1 or dfs(y_match[it],x_match,y_match,vis))
	  	    	  {
	  	    	  	x_match[now] = it;
	  	    	  	y_match[it] = now;
	  	    	  	return 1;
					}
			  }  
		return 0;
	  }
	ll maxmatch(ll n,ll m)
	  {
	  	ll ret = 0;
	  	vector<ll> x_match(n + 1,-1),y_match(m + 1,-1);
	  	//for (ll i = n;i >= 1;i--)
	  	for (ll i = 1;i <= n;i++)
	  	  {
			vector<ll> vis(m + 1);
			ret += dfs(i,x_match,y_match,vis);
			}
		return ret;
	  }
  }

int main()
  {
  	ll n,m,e;
  	cin >> n >> m >> e;
  	Km::init(n,m);
  	for (ll i = 0;i < e;i++)
  	  {
  	  	ll x,y;
  	  	cin >> x >> y;
  	  	if (x < 1 or x > n or y < 1 or y > m)
  	  	  continue;
  	  	Km::add_edge(x,y);
		}
	cout << Km::maxmatch(n,m);
  }

生成树

DSU

值得一提的是make_root,可以把一个元素变成代表元

namespace Dsu
  {
  	vector<ll> f;
  	void init(ll n)
  	  {
  	  	f.resize(n + 1);
		}
	ll find(ll n)
	  {
	  	if (f[n] == 0)
	  	  return n;
	  	else
	  	  return f[n] = find(f[n]);
	  }
	bool is_same(ll a,ll b)
	  {
	  	return find(a) == find(b); 
	  }
	void uni(ll a,ll b)
	  {
	  	if (find(a) != find(b))
	  	  f[find(a)] = find(b);
	  }
        void make_root(ll now)
	  {
	  	find(now);
	  	if (f[now] != 0)
	  	  {
	  	  	ll fa = f[now];
	  	  	f[fa] = now;
	  	  	f[now] = 0;
			} 
	  }
  }

克鲁斯卡尔

复杂度\(O(V + Elog(E))\)

prim算法

复杂度\(O((V + E)logV)\)
值得注意的是和迪杰斯特拉算法相比
迪杰斯特拉 : priorirty_queue.push(min_distance[now] + edge.cost)
prim : priorirty_queue.push(edge.cost)

最短路

定义:
V:图中的点数
E:图中的边数

链式前向星 \(O(V + E)\)

常数小

#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
namespace Forward_Star
  {
  	vector<tuple<ll,ll,ll>> edge; //(to,next,weight)
  	vector<ll> head;
	void init(ll n)
  	  {
  	  	head = vector<ll>(n + 1,-1);
		}
  	void add_edge(ll a,ll b,ll c)
  	  {
  	  	edge.emplace_back(b,head[a],a);
  	  	head[a] = edge.size() - 1;
		}
	void show(ll n)
	  {
	  	for (ll i = 1;i <= n;i++)
	  	  {
	  	  	for (ll k = head[i];k != -1;k = get<1>(edge[k]))
			  {
			    ll from = i;
				ll to,weight;
				std::tie(to,std::ignore,weight) = edge[k];	
				cout << "debug+ " << from << " " << to << " " << weight << "\n";	
				 } 
			}
	  }
  }
int main()
{
    ll n,m; 
    cin >> n >> m;
    Forward_Star::init(n);
    for (ll i = 1;i <= m;i++)
      {
      	ll a,b,c;
      	cin >> a >> b >> c;
      	Forward_Star::add_edge(a,b,c);
	  }
	Forward_Star::show(n);
	return 0;
}
/*
Test data : 
5 7
1 2 1
2 3 2
3 4 3
1 3 4
4 1 5
4 5 6
4 5 7
*/

floyd \(O(V^3)\)

/*
---- From XDU's mzb
*/
#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
namespace Floyd
  {
  	vector<tuple<ll,ll,ll>> edge;
  	void clear()
  	  {
  	  	edge.clear();
		}
  	void add_edge(ll a,ll b,ll c)
  	  {
  	  	edge.emplace_back(a,b,c);
		}
	vector<vector<ll>> run(ll n)
	  {
	  	vector<vector<ll>> ret(n + 1,vector<ll>(n + 1,numeric_limits<ll>::max()));
	  	for (ll i = 1;i <= n;i++)
	  	  ret[i][i] = 0;
	  	for (auto it : edge)
	  	  {
	  	  	ll a,b,c;
	  	  	tie(a,b,c) = it;
	  	  	ret[a][b] = min(ret[a][b],c);
			}
	  	for (ll k = 1;k <= n;k++) // 枚举中转点 
	  	  for (ll i = 1;i <= n;i++)
	  	    for (ll j = 1;j <= n;j++)
	  	      {
	  	      	if (ret[i][k] != numeric_limits<ll>::max() and ret[k][j] != numeric_limits<ll>::max()) 
	  	      	  {
	  	      	  	ret[i][j] = min(ret[i][j],ret[i][k] + ret[k][j]);
					  }
				}
		return ret;
	  }
  }
int main()
  {
  	ll n,m,star;
  	cin >> n >> m >> star;
  	for (ll i = 1;i <= m;i++)
  	  {
  	  	ll a,b,c;
  	  	cin >> a >> b >> c;
  	  	Floyd::add_edge(a,b,c);
		}
	auto fin = Floyd::run(n);
	auto ans = fin[star];
	for (ll i = 1;i < ll(ans.size());i++)
	  cout << ans[i] << (i + 1 == ll(ans.size()) ? "" : " ");
	return 0;
  }

dijkstra \(O((V + E)log_2{E})\)

#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
namespace Dijkstra
  {
  	vector<pair<ll,ll>> g[100000 + 110];
  	void clear(ll n)
  	  {
  	  	for (ll i = 1;i <= n;i++)
  	  	  g[i].clear();
		}
  	void add_edge(ll a,ll b,ll c)
  	  {
  	  	g[a].emplace_back(b,c);
		}
	vector<ll> run(ll n,ll star)
	  {
	  	vector<ll> min_distance(n + 1,numeric_limits<ll>::max());
		priority_queue<pair<ll,ll>,vector<pair<ll,ll>>,greater<pair<ll,ll>>> min_queue;
	    min_queue.emplace(0,star);
	    while (!min_queue.empty())
	      {
	        auto now = min_queue.top();min_queue.pop();
	      	if (min_distance[now.second] != numeric_limits<ll>::max())
	      	  continue;
	      	else
	      	  min_distance[now.second] = now.first;
	      	for (auto it : g[now.second])
	      	  if (min_distance[it.first] == numeric_limits<ll>::max())
	      	  	min_queue.emplace(it.second + min_distance[now.second],it.first); 
		  }
	  	return min_distance;
	  }
  }
int main()
{
    ll n,m,star;
    cin >> n >> m >> star;
    for (ll i = 1;i <= m;i++)
      {
      	ll a,b,c;
      	cin >> a >> b >> c;
      	Dijkstra::add_edge(a,b,c);
	  }
	auto ans = Dijkstra::run(n,star);
	for (ll i = 1;i < ll(ans.size());i++)
	  cout << ans[i] << (i + 1 == ll(ans.size()) ? "": " "); 
    return 0;
}

bellman-ford \(O(VE)\)

增广V轮后还能增广,就是存在负权回路

#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
namespace Bellman_Ford
  {
  	vector<tuple<ll,ll,ll>> edge;
  	void clear(ll n)
  	  {
  	  	edge.clear();
		}
  	void add_edge(ll a,ll b,ll c)
  	  {
  	  	edge.emplace_back(a,b,c);
		}
	bool relax(vector<ll> & min_distance,ll a,ll b,ll c)
	  {
	  	if (min_distance[a] != numeric_limits<ll>::max())
	  	  {
	  	  	if (min_distance[a] + c < min_distance[b])
	  	  	  {
	  	  	  	min_distance[b] = min_distance[a] + c;
	  	  	  	return 1;
				  }
			}
		return 0;
	  }
	vector<ll> run(ll n,ll star)
	  {
	  	vector<ll> min_distance(n + 1,numeric_limits<ll>::max());
	  	min_distance[star] = 0;
	  	while (1)
	  	  {
	  	  	bool is_relaxed = 0;
	  	  	for (auto it : edge)
	  	  	  {
	  	  	  	ll a,b,c;
	  	  	  	tie(a,b,c) = it;
				is_relaxed |= relax(min_distance,a,b,c);
				  }
			if (!is_relaxed)
			  break;
			}
	  	return min_distance;
	  }
  }
int main()
{
    ll n,m,star;
    cin >> n >> m >> star;
    for (ll i = 1;i <= m;i++)
      {
      	ll a,b,c;
      	cin >> a >> b >> c;
      	Bellman_Ford::add_edge(a,b,c);
	  }
	auto ans = Bellman_Ford::run(n,star);
	for (ll i = 1;i < ll(ans.size());i++)
	  cout << ans[i] << (i + 1 == ll(ans.size()) ? "": " "); 
    return 0;
}

SPFA

期望\(O(kE)\),其中k是一个常数
最坏\(O(VE)\)
Bellman_Ford的一个队列优化,思路没什么创新
咕咕咕

Johnson全源最短路

稠密图上弗洛伊德算法最快
先来一次BellmanFord算法,把图变成正权图,然后来n次Dijkstra,复杂度\(O(V^2logV + VE)\)
单纯给所有边加正权是没有用的,没有改变负权图的根本性质

edge old,new;
ll star = n + 1;
for (ll i = 1;i <= n;i++)
  {
  	old.add_edge(star,i,0);
	}
auto min_distance = Bellman_Ford(old,n + 1,star);
for (auto it : edge)
  {
  	tie(a,b,c) = it;
  	new.add_edge(a,b,c + min_distance[a] - min_distance[b]);
  }
for (ll i = 1;i <= n;i++)
  Dijkstra(new,i);
证明

当确定源点,跑一次最短路后,min_distance就拥有类似势能的性质
也就是(min_distance[a] - min_distance[b]) - (min_distance[b] - min_distance[c]) = min_distance[a] - min_distance[c]
最短路性:

非负性:有三角不等式,可简单证明新图的边权都非负

差分约束

解由若干\(x_i - x_j \leq b_k\)组成的方程
建一个超级源点 n + 1 并向所有点连权值为 0 的边是为了求一组非正数的可行解

for (ll i = 1;i <= n;i++)
  {
  	Bellman_Ford::add_edge(n + 1,i,0);
  }
for (ll i = 1;i <= m;i++)
  {
  	ll x_i,x_j,b_k;
  	cin >> x_i >> x_j >> b_k;
  	Bellman_Ford::add_edge(x_j,x_i,b_k);
  }
bool has_ans = Bellman_Ford::run(n + 1,n + 1);

然后从超级源(n + 1)开始跑最短路就可以
有负权回路就无解,反之有解,解为
\(\{x_1,x_2...x_n\} = \{ min\_distance(源,v_1),min\_distance(源,v_2)...min\_distance(源,v_n)\}\)
相较于其他解,这个解满足\(\sum x_i\)最大同时 \((\max x_i - \min x_i)\)最小
注意我们在连源和其他点的时候,意味着加了约束\(x_i \leq 0\)

拓扑排序

#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
namespace Topological_Sorting
  {
  	vector<vector<ll>> g;
	void init(ll n)
  	  {
  	  	g = vector<vector<ll>>(n + 1);
		}
  	void add_edge(ll a,ll b)
  	  {
  	  	g[a].emplace_back(b);
		}
	vector<ll> run(ll n)
	  {
	  	vector<ll> degree(n + 1);
	  	for (ll i = 1;i <= n;i++)
	  	  {
	  	  	for (auto it : g[i])
	  	  	  degree[it]++;
			}
	  	queue<ll> que;
	  	for (ll i = 1;i <= n;i++)
	  	  if (degree[i] == 0)
	  	    que.emplace(i);
	  	vector<ll> ret;
	  	while (que.size())
	  	  {
	  	  	ll now = que.front();que.pop();
	  	    for (auto it : g[now])
	  	      {
	  	      	degree[it]--;
	  	      	if (degree[it] == 0)
	  	      	  que.emplace(it);
				}
			ret.emplace_back(now);
			}
	  	return ll(ret.size()) == n ? ret : vector<ll>();
	  }
  }
int main()
{
    ll n,m; 
    cin >> n >> m;
    Topological_Sorting::init(n);
    for (ll i = 1;i <= m;i++)
      {
      	ll a,b;
      	cin >> a >> b;
      	Topological_Sorting::add_edge(a,b);
	  }
	auto ret = Topological_Sorting::run(n);
	for (auto it : ret)
	  cout << it << " ";
	return 0;
}

连通性问题

dfs树


dfs树里面只有两种边
父子边 : 黑边
返祖边 : 绿边
下面两种边不出现
横向边 : 红边
正向边 : 也就是调到dfs树子孙的非父子边
dfs的时候,
\(d[v]\)为,当顶点v被第一次发现时,的时间戳 (发现时间)
\(f[v]\)为,当顶点v的所有相邻点都被dfs后,的时间戳 (结束时间)

Tarjan 强连通分量

相当于给点分了组,组内强联通
dfn就是时间序
low就是该点能走到的,时间序最小的节点,的时间序
那么很显然,当\(low[now] = dfn[now]\)的时候,now就是dfs树上强连通分量的根
把所有被dfn[now]标记的点退栈即可

namespace Tarjan
  {
  	vector<vector<ll>> g;
  	vector<ll> low,dfn,vis;
  	std::stack<ll> stack;
  	void init(ll n)
  	  {
  	  	g = vector<vector<ll>>(n + 1);
  	  	low = dfn = vis = vector<ll>(n + 1);
  	  	stack = std::stack<ll>();
		}
	void add_edge(ll a,ll b)
	  {
	  	g[a].emplace_back(b);
	  }
	void run(ll now,vector<ll> & id,ll & time)
	  {
	  	low[now] = dfn[now] = ++time;
	    stack.emplace(now);
	  	vis[now] = 1;
	  	for (auto it : g[now])
	  	  {
	  	  	if (!dfn[it])
	  	  	  {
	  	  	  	run(it,id,time);
	  	  	  	low[now] = min(low[now],low[it]);
				  }
			else if (vis[it])
			  {
			  	low[now] = min(low[now],low[it]);
			  }
			}
		if (dfn[now] == low[now])
		  {
		    while (stack.size() and stack.top())
		  	  {
		  	  	ll t = stack.top();
		  	  	id[t] = now;
		  	  	vis[t] = 0;
		  	  	stack.pop();
		  	  	if (now == t)
		  	  	  break;
				}
		  }
	  }
	vector<ll> run(ll n)
	  {
	  	vector<ll> ret(n + 1);
	  	ll time = 0;
	  	for (ll i = 1;i <= n;i++)
	  	  if (!dfn[i])
	  	    run(i,ret,time);
	  	return ret;
	  }
  }

tarjin求割点

#include <iostream>
#include <vector>
#include <cstring>

using std::vector;
using std::string;
using std::cout;
using std::cin;
using std::min;

struct CutPoint {
	
	static const int maxn = 200000 + 10;
	
	int dfn[maxn];
	int low[maxn];
	int cut[maxn]; 
	int dfs_clock;
	vector<int> e[maxn];
	int n;
	
	CutPoint(int n)
		:n(n),dfs_clock(0)
		{
			memset(cut,0,sizeof(cut));
		}
	
	void addEdge(int a,int b) {
		e[a].emplace_back(b);
		e[b].emplace_back(a);
	}
	vector<int> getCutPoint() {
		dfs_clock = 0;
    	for(int i = 1;i <= n;i++) {
    	// 假设不是连通图 ,连通图跑一次就行了
    		if (!dfn[i]) {
				tarjan(i,i,-1);
			}
		} 
		vector<int> result;
		for (int i = 1;i <= n;++i) {
			if (cut[i]) {
				result.push_back(i);
			}
		}
		return result;
	}
	void tarjan(int now,int root,int fa) {
		//记录当前节点、树的根节点、父节点
	    dfn[now] = low[now] = ++dfs_clock;
	    //初始时,low[now] = dfn[now]
	    int child = 0;//记录该节点的孩子数
	    for (auto to : e[now]) {
	    	if(!dfn[to]) {
	    		// 若 to 尚未访问过
	            child++;//to是now的孩子
	            tarjan(to,root,now);//向下遍历
	            low[now] = min(low[now],low[to]);//更新now
	            if (low[to] >= dfn[now] and now != root) {
	            	cut[now] = 1;
				} 
	        }
	        else if (to != fa) {
	        	low[now] = min(low[now],dfn[to]);
			} 
	        //若to已访问过,且to不是父节点,则to是祖先节点
	    }
	    if (child >= 2 and now == root) {
	    	cut[now] = 1;
		}
	}
};
int main() {
	int n,m;
    cin >> n >> m;
    CutPoint cut(n);
    for (int i = 1;i <= m;++i) {
    	int a,b;
    	cin >> a >> b;
    	cut.addEdge(a,b);
	}
    auto result = cut.getCutPoint();
    cout << result.size() << "\n";
    for (auto it : result) {
    	cout << it << " ";
	}
    return 0;
}
posted @ 2021-10-11 15:08  XDU18清欢  阅读(59)  评论(0)    收藏  举报