无向图上割点/割边

无向图上割点/割边

下面的四个算法都是针对无向图的

割点

注意:割点不判断前一个边(判断也行) 割边一定需要判断前一个边

解释:对于 (u,v) 这条边 我们如果让 v 更新 (v,u) 这条边 这个 v 节点还是只能最靠上回溯到 u 节点 对答案是没有影响的

但是割边则不同 如果你让 v 更新了 (v,u) 这条边 那么这条边的 low[v] 将会被错误地更新为 dfn[u] 导致 low[v] 永远会大于等于 dfn[u] 导致最后搜不出来割边

vfa 是错的!!!!!!!!!!!!!!必须判断反向边!!!!!!!!!!!!!!

vfa 是错的!!!!!!!!!!!!!!必须判断反向边!!!!!!!!!!!!!!

vfa 是错的!!!!!!!!!!!!!!必须判断反向边!!!!!!!!!!!!!!

P3388 【模板】割点(割顶)

tarjan基础操作 相比于缩点有所不同:

  1. 如果一个点u为根 且有至少两个子树(这两个子树必须是两个独立的子树 即互相之间不能到达) 那么是割点
  2. 如果一个点u不是根 且 low[v]dfn[u] 说明子节点 v 不能通过非树边到达 u 以上的节点 则必然可以通过割掉 u 来使得 v 不能和 u 前面的点连通 那么是割点

v 节点有 dfn 值的时候 我们一定要更新 low[u]=min(low[u],dfn[v]) 因为点 u 不一定能到达 v 能到达的所有节点

还需要注意:一个点作为割点可能会被标记多次 我们只需要标记 在最后统计即可 或者可以用 vector 记录并去重

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid (l+r>>1)
#define inl inline
#define eb emplace_back
#define pii pair<int,int>
#define fi first
#define se second
const int N = 2e4 + 5;
const int inf = 0x3f3f3f3f;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
// #define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , pd[N] , cnt;

vector<int> e[N];
inl void add ( int u , int v ) { e[u].eb(v); }

int dfn[N] , low[N] , timer;

void tarjan ( int u , int rt )
{
	dfn[u] = low[u] = ++timer;
	int child = 0;
	for ( auto v : e[u] )
	{
		if ( !dfn[v] )
		{
			tarjan ( v , rt ) , low[u] = min ( low[u] , low[v] );
			if ( low[v] >= dfn[u] && u != rt ) pd[u] = 1;
			++ child;
		}
		else low[u] = min ( low[u] , dfn[v] );
	}
	if ( u == rt && child >= 2 ) pd[u] = 1;
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read();
	for ( int i = 1 , u , v ; i <= m ; i ++ ) u = read() , v = read() , add ( u , v ) , add ( v , u );
	for ( int i = 1 ; i <= n ; i ++ ) if ( !dfn[i] ) tarjan(i,i);
	for ( int i = 1 ; i <= n ; i ++ ) if ( pd[i] ) cnt ++;
	cout << cnt << endl;
	for ( int i = 1 ; i <= n ; i ++ ) if ( pd[i] ) cout << i << ' ';
	return 0;
}

P3469 [POI2008] BLO-Blockade

显然对于每一个割点 如果删去它形成的连通块大小分别为 s1k 那么答案就是 i=1ksi(nsi1)

(我们对于 u 节点的贡献需要单独计算 注意 不要忘记 u 没有被删去 它本身算一个大小为 1 的连通块)

如果不是割点 那么答案即为 2(n1)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid (l+r>>1)
#define inl inline
#define eb emplace_back
#define pii pair<int,int>
#define fi first
#define se second
#define int long long
const int N = 1e5 + 5;
// char buf[1<<24] , *p1 , *p2;
// #define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
#define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , ans[N] , cut[N] , sz[N];

vector<int> e[N];
inl void add ( int u , int v ) { e[u].eb(v); }

int dfn[N] , low[N] , timer;

void tarjan ( int u , int rt )
{
	int child = 0 , sum = 0;
	dfn[u] = low[u] = ++ timer , sz[u] = 1;
	for ( auto v : e[u] )
		if ( !dfn[v] )
		{
			tarjan ( v , rt ) , low[u] = min ( low[u] , low[v] ) , sz[u] += sz[v];
			if ( dfn[u] <= low[v] )
			{
				ans[u] += ( n - sz[v] - 1 ) * sz[v] , sum += sz[v];
				if ( u != rt ) cut[u] = 1;
			}
			++ child;
		}
		else low[u] = min ( low[u] , dfn[v] );
	if ( u == rt && child >= 2 ) cut[u] = 1;
	if ( cut[u] ) ans[u] += sum * ( n - sum - 1 );
	ans[u] += 2 * ( n - 1 );
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read();
	for ( int i = 1 , u , v ; i <= m ; i ++ ) u = read() , v = read() , add ( u , v ) , add ( v , u );
	for ( int i = 1 ; i <= n ; i ++ ) if ( !dfn[i] ) tarjan ( i , i );
	for ( int i = 1 ; i <= n ; i ++ ) cout << ans[i] << endl;
	return 0;
}

P5058 [ZJOI2004] 嗅探器

为了简化题目 我们的 tarjan 从其中一个信息中心开始跑即可 那么一个合法的割点当且仅当 low[v]dfn[u]dfn[vv]dfn[v]

这里第二个柿子是因为我们现在看的是割掉 u 点 能否让 (u,v) 这条边能连通的子树内的所有点变得不联通了

那么 dfn[vv]dfn[v] 当且仅当 vvu 子树中(不包括 u )

这样求出所有割点即可

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid (l+r>>1)
#define inl inline
#define eb emplace_back
#define pii pair<int,int>
#define fi first
#define se second
const int N = 2e5 + 5;
//char buf[1<<24] , *p1 , *p2;
//#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
 #define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , uu , vv , cut[N];

vector<int> e[N];
inl void add ( int u , int v ) { e[u].eb(v); }

int dfn[N] , low[N] , timer;

void tarjan ( int u , int rt )
{
	dfn[u] = low[u] = ++timer; 
	for ( auto v : e[u] )
	{
		if ( !dfn[v] )
		{
			tarjan(v,rt) , low[u] = min ( low[u] , low[v] );
			if ( low[v] >= dfn[u] && u != rt && dfn[vv] >= dfn[v] ) cut[u] = 1;
		}
		else low[u] = min ( low[u] , dfn[v] );		
	}
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read();
	while ( ( uu = read() ) + ( vv = read() ) != 0 ) add ( uu , vv ) , add ( vv , uu );
	uu = read() , vv = read();
	tarjan(uu,uu);
	for ( int i = 1 ; i <= n ; i ++ ) if ( cut[i] ) { cout << i << endl; return 0; }
	return cout << "No solution" << endl , 0;
}

割边

相当于在割点板子上改了一下判断条件 low[v]>dfn[u] 而且需要特判反向边

这样是为了防止搜索 v 的时候 将 low[v] 错误地更新为了 dfn[u] 导致 low[v] 永远会大于等于 dfn[u] 导致最后搜不出来割边

P1656 炸铁路

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid (l+r>>1)
#define inl inline
#define eb emplace_back
#define pii pair<int,int>
#define fi first
#define se second
const int N = 1e5 + 5;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
// #define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , k , l , sza[N] , szb[N] , cnt = 1;

vector<pii> e[N] , ans;
inl void add ( int u , int v , int w ) { e[u].eb(v,w); }

int low[N] , dfn[N] , timer;

void tarjan ( int u , int ff )
{
	dfn[u] = low[u] = ++timer;
	for ( auto [v,p] : e[u] )
	{
		if ( !dfn[v] )
		{
			tarjan ( v , p ) , low[u] = min ( low[u] , low[v] ) , sza[u] += sza[v] , szb[u] += szb[v];
			if ( low[v] > dfn[u] && ( sza[v] == 0 || szb[v] == 0 || sza[v] == k || szb[v] == l ) ) ans.eb(u,v);
		}
		else if ( p ^ ( ff ^ 1 ) ) low[u] = min ( low[u] , dfn[v] );
	}
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read() , k = read() , l = read();
	for ( int i = 1 ; i <= k ; i ++ ) sza[read()] = 1;
	for ( int i = 1 ; i <= l ; i ++ ) szb[read()] = 1;
	for ( int i = 1 , u , v ; i <= m ; i ++ ) u = read() , v = read() , add ( u , v , ++ cnt ) , add ( v , u , ++ cnt );
	tarjan(1,0);
	cout << ans.size() << endl;
	for ( auto p : ans ) cout << p.fi << ' ' << p.se << endl;
	return 0;
}

P7687 [CEOI2005] Critical Network Lines

割边板子

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid (l+r>>1)
#define inl inline
#define eb emplace_back
#define pii pair<int,int>
#define fi first
#define se second
const int N = 1e5 + 5;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
// #define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , k , l , sza[N] , szb[N] , cnt = 1;

vector<pii> e[N] , ans;
inl void add ( int u , int v , int w ) { e[u].eb(v,w); }

int low[N] , dfn[N] , timer;

void tarjan ( int u , int ff )
{
	dfn[u] = low[u] = ++timer;
	for ( auto [v,p] : e[u] )
	{
		if ( !dfn[v] )
		{
			tarjan ( v , p ) , low[u] = min ( low[u] , low[v] ) , sza[u] += sza[v] , szb[u] += szb[v];
			if ( low[v] > dfn[u] && ( sza[v] == 0 || szb[v] == 0 || sza[v] == k || szb[v] == l ) ) ans.eb(u,v);
		}
		else if ( p ^ ( ff ^ 1 ) ) low[u] = min ( low[u] , dfn[v] );
	}
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read() , k = read() , l = read();
	for ( int i = 1 ; i <= k ; i ++ ) sza[read()] = 1;
	for ( int i = 1 ; i <= l ; i ++ ) szb[read()] = 1;
	for ( int i = 1 , u , v ; i <= m ; i ++ ) u = read() , v = read() , add ( u , v , ++ cnt ) , add ( v , u , ++ cnt );
	tarjan(1,0);
	cout << ans.size() << endl;
	for ( auto p : ans ) cout << p.fi << ' ' << p.se << endl;
	return 0;
}

点双

点双/边双针对无向图

tarjan 中 如果我们搜到了一个割点 那么将现在栈中的所有节点全部弹出来 但是根节点不能出栈 因为割点一定属于多个点双

P8435 【模板】点双连通分量

代码中的特判是针对孤立点的() 如果我们想要用有没有边来判断孤立点的话 记得需要去重边

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid (l+r>>1)
#define inl inline
#define eb emplace_back
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define getchar() cin.get();
const int N = 5e5 + 5;
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , mod , a[N] , tot;

vector<int> e[N] , bcc[N];
inl void add ( int u , int v ) { e[u].eb(v); }

int low[N] , dfn[N] , timer;
int sta[N] , top;

void tarjan ( int u , int rt )
{
	low[u] = dfn[u] = ++timer;
	sta[++top] = u;
	for ( auto v : e[u] )
		if ( !dfn[v] ) 	
		{
			tarjan ( v , rt ) , low[u] = min ( low[u] , low[v] );
			if ( dfn[u] <= low[v] )
			{
				++ tot;
				while ( top )
				{
					int x = sta[top--]; bcc[tot].eb(x);
					if ( x == v ) break;
				} 
				bcc[tot].eb(u);
			}
		}
		else low[u] = min ( low[u] , dfn[v] ); 
	if ( u == rt && !e[u].size() ) bcc[++tot].eb(u);
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read();
	for ( int i = 1 , u , v ; i <= m ; i ++ )
	{
		u = read() , v = read();
		if ( u != v ) add ( u , v ) , add ( v , u );
	}
	for ( int i = 1 ; i <= n ; i ++ ) if ( !dfn[i] ) tarjan ( i , i );
	cout << tot << endl;
	for ( int i = 1 ; i <= tot ; i ++ )
	{
		cout << bcc[i].size() << ' ';
		for ( auto v : bcc[i] ) cout << v << ' ';
		cout << endl;
	}
	return 0;
}

边双

相当于是将题中所有的割边都去掉 形成的联通块 那么开一个栈来进行类 tarjan 统计即可

而且 我们可能加了两条及以上 (u,v) 边 这是可以满足从 u 通过返祖边回到 father 的 那么如果我们只判断 father 就扼杀了另一条合法 (u,v) 的可能

所以我们需要记录进边的值来判断是否向下搜索

P8436 【模板】边双连通分量

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
const int N = 5e5 + 5;
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m , sta[10000000] , top , cnt = 1;

vector<pii> e[N];
vector<int> bcc[N];
inl void add ( int u , int v , int w ) { e[u].eb(v,w); }

int tot , dfn[N] , low[N] , timer;

void tarjan ( int u , int ff )
{
	low[u] = dfn[u] = ++timer;
	sta[++top] = u;
	for ( auto [v,k] : e[u] )
	{
		if ( !dfn[v] ) tarjan ( v , k ) , low[u] = min ( low[u] , low[v] );
		else if ( k != ( ff ^ 1 ) ) low[u] = min ( low[u] , dfn[v] );
	}
	if ( low[u] == dfn[u] )
	{
		++ tot;
		while ( top )
		{
			int x = sta[top--]; bcc[tot].eb(x);
			if ( x == u ) break;
		}
	}
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read();
	for ( int i = 1 , u , v ; i <= m ; i ++ ) u = read() , v = read() , add ( u , v , ++ cnt ) , add ( v , u , ++ cnt );
	for ( int i = 1 ; i <= n ; i ++ ) if ( !dfn[i] ) tarjan ( i , 0 );
	cout << tot << endl;
	for ( int i = 1 ; i <= tot ; i ++ )
	{
		cout << bcc[i].size() << ' ';
		for ( auto v : bcc[i] )
			cout << v << ' ' ;
		cout << endl;
	}
	return 0;
}

P2860 [USACO06JAN] Redundant Paths G

缩点之后 统计一下入度大小

如果叶子节点 s 为偶数 那么两两连边即可 答案是 s/2

如果是奇数 那么是 s/2+1

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
const int N = 1e5 + 5;
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m , cnt = 1 , u[N] , v[N] , ans , dag[N];

int dfn[N] , low[N] , timer;
int sta[N] , top;
int id[N] , tot;

vector<pii> e[N];
inl void add ( int u , int v , int w ) { e[u].eb(v,w); }

void tarjan ( int u , int ff )
{
	low[u] = dfn[u] = ++timer;
	sta[++top] = u;
	for ( auto [v,k] : e[u] )
	{
		if ( !dfn[v] ) tarjan ( v , k ) , low[u] = min ( low[u] , low[v] );
		else if ( k ^ ( ff ^ 1 ) ) low[u] = min ( low[u] , dfn[v] );
	}
	if ( low[u] == dfn[u] )
	{
		++ tot;
		while ( top )
		{
			int x = sta[top--];
			id[x] = tot;
			if ( x == u ) break;
		}
	}
}


signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read();
	for ( int i = 1 ; i <= m ; i ++ ) u[i] = read() , v[i] = read() , add ( u[i] , v[i] , ++ cnt ) , add ( v[i] , u[i] , ++ cnt ); 
	for ( int i = 1 ; i <= n ; i ++ ) if ( !dfn[i] ) tarjan ( i , 0 );
	for ( int i = 1 ; i <= m ; i ++ ) if ( id[u[i]] != id[v[i]] ) ++ dag[id[u[i]]] , ++ dag[id[v[i]]];
	for ( int i = 1 ; i <= tot ; i ++ ) ans += ( dag[i] == 1 );
	return cout << ans / 2 + ( ans & 1 ) << endl , 0;
}

圆方树

广义圆方树即为:在无向图中将所有点双缩起来 作为一个方点放在树中 圆点就是树上原有的节点

P4320 道路相遇

题意显然可以转化为 q 次询问 (u,v) 之间的割点数量

那么建立圆方树并树上差分统计即可 统计的是圆点的数量

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
const int N = 1e6 + 5;
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m , q , cnt , block , w[N] , sum[N];

int dfn[N] , low[N] , timer;
int sta[N] , tp;

vector<int> e[N] , ee[N];
inl void add ( int u , int v ) { e[u].eb(v); }
inl void adde ( int u , int v ) { ee[u].eb(v); }

void tarjan ( int u )
{
	dfn[u] = low[u] = ++timer;
	sta[++tp] = u;
	for ( auto v : e[u] )
	{
		if ( !dfn[v] )
		{
			tarjan ( v ) , low[u] = min ( low[u] , low[v] );
			if ( low[v] >= dfn[u] )
			{
				++ block;
				while ( tp )
				{	
					int x = sta[tp--];
					adde ( block , x ) , adde ( x , block ) , w[x] = 1;
					if ( x == v ) break;
				}
				adde ( block , u ) , adde ( u , block ) , w[u] = 1;
			}
		}		
		else low[u] = min ( low[u] , dfn[v] );
	}
}

int fa[N] , dep[N] , sz[N] , son[N];
int top[N] , rev[N] , pos[N];

struct LCA
{
	void dfs1 ( int u , int ff )
	{
		sum[u] = sum[fa[u]=ff] + w[u] , dep[u] = dep[ff] + 1 , sz[u] = 1;
		for ( auto v : ee[u] )
			if ( v ^ ff )
			{
				dfs1 ( v , u );
				sz[u] += sz[v];
				if ( sz[son[u]] < sz[v] ) son[u] = v;
			}
	}
	void dfs2 ( int u , int tp )
	{
		top[u] = tp , pos[u] = ++timer , rev[timer] = u;
		if ( son[u] ) dfs2 ( son[u] , tp );
		for ( auto v : ee[u] ) if ( v ^ fa[u] && v ^ son[u] ) dfs2 ( v , v );
	}
	int lca ( int u , int v )
	{
		while ( top[u] != top[v] )
		{
			if ( dep[top[u]] < dep[top[v]] ) swap ( u , v );
			u = fa[top[u]];
		}
		if ( dep[u] < dep[v] ) swap ( u , v );
		return v;
	}
}L;

int query ( int u , int v )
{
	int lcaa = L.lca(u,v);
	return sum[u] + sum[v] - sum[lcaa] - sum[fa[lcaa]];
}


signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read() , block = n;
	for ( int i = 1 , u , v ; i <= m ; i ++ ) u = read() , v = read() , add ( u , v ) , add ( v , u );
	for ( int i = 1 ; i <= n ; i ++ ) if ( !dfn[i] ) tarjan ( i );
	timer = 0;
	L.dfs1 ( 1 , 0 ) , L.dfs2 ( 1 , 1 );
	q = read();
	for ( int i = 1 ; i <= q ; i ++ )
	{
		int u = read() , v = read();
		cout << query ( u , v ) << endl;
	}
	return 0;
}

P4606 [SDOI2018] 战略游戏

显然我们求的是 一个集合中任意两点的路径并的子图中的白点数量除 2 再减去点集的大小(不重复)

如果直接求点的并的话不好统计

那么我们将节点的权值放到它和它父亲连的边上 那么所有路径上不重复白点的个数就是每两个 dfs 序相邻的点的路径的并除 2 再减去点集的大小 (画图可知)

但是这样 1n 节点的 lca 不会被统计到 特判一下即可

注意 我们在新树中排序的时候 注意要用新树的 pos 数组来排序 而不是老树的 dfn 数组

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
const int N = 1e6 + 5;
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m , q , cnt , block , w[N] , ans , a[N];

int dfn[N] , low[N] , timer;
int sta[N] , tp;

vector<int> e[N] , ee[N];
inl void add ( int u , int v ) { e[u].eb(v); }
inl void adde ( int u , int v ) { ee[u].eb(v); }

void tarjan ( int u )
{
	dfn[u] = low[u] = ++timer;
	sta[++tp] = u;
	for ( auto v : e[u] )
		if ( !dfn[v] )
		{
			tarjan(v) , low[u] = min ( low[u] , low[v] );
			if ( low[v] >= dfn[u] )
			{
				++ block;
				while ( tp )
				{
					int x = sta[tp--];
					adde ( block , x ) , adde ( x , block ) , w[x] = 1;
					if ( x == v ) break;
				}
				adde ( block , u ) , adde ( u , block ) , w[u] = 1;
			}
		}
		else low[u] = min ( low[u] , dfn[v] );
}

int sz[N] , fa[N] , dep[N] , sum[N] , son[N];
int tim , rev[N] , pos[N] , top[N];

struct node 
{
	void dfs1 ( int u , int ff )
	{
		fa[u] = ff , sum[u] = sum[ff] + w[u] , dep[u] = dep[ff] + 1 , sz[u] = 1;
		for ( auto v : ee[u] )
			if ( v ^ ff )
			{
				dfs1 ( v , u );
				sz[u] += sz[v];
				if ( sz[son[u]] < sz[v] ) son[u] = v;
			}
	}
	void dfs2 ( int u , int tp )
	{
		top[u] = tp , pos[u] = ++ tim , rev[tim] = u;
		if ( son[u] ) dfs2 ( son[u] , tp );
		for ( auto v : ee[u] ) if ( v ^ fa[u] && v ^ son[u] ) dfs2 ( v , v );
	}
	int lca ( int u , int v )
	{
		while ( top[u] != top[v] )
		{
			if ( dep[top[u]] < dep[top[v]] ) swap ( u , v );
			u = fa[top[u]];
		}
		if ( dep[u] < dep[v] ) swap ( u , v );
		return v;
	}
}L;

void init()
{
	for ( int i = 1 ; i <= 2 * n ; i ++ ) e[i].clear();
	for ( int i = 1 ; i <= 2 * n ; i ++ ) ee[i].clear();
	memset ( son , 0 , sizeof son );
	memset ( w , 0 , sizeof w );
	memset ( dfn , 0 , sizeof dfn );
	memset ( sum , 0 , sizeof sum );
	tim = timer = tp = ans = 0;
}

int dis ( int u , int v )
{
	int lcaa = L.lca ( u , v );
	return sum[u] +	sum[v] - 2 * sum[lcaa];
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	int T = read();
	while ( T -- )
	{
		init();
		n = read() , m = read() , block = n;
		for ( int i = 1 , u , v ; i <= m ; i ++ ) u = read() , v = read() , add ( u , v ) , add ( v , u );
		for ( int i = 1 ; i <= n ; i ++ ) if ( !dfn[i] ) tarjan(i);
		L.dfs1 ( 1 , 0 ) , L.dfs2 ( 1 , 1 );
		q = read();	
		for ( int i = 1 ; i <= q ; i ++ )
		{
			ans = 0;
			int s = read();
			for ( int j = 1 ; j <= s ; j ++ ) a[j] = read();
			sort ( a + 1 , a + s + 1 , [](const int &a , const int &b) { return pos[a] < pos[b]; } );
			a[s+1] = a[1];
			for ( int j = 1 ; j <= s ; j ++ ) ans += dis ( a[j] , a[j+1] );
			ans >>= 1 , ans -= s;
			ans += w[L.lca(a[1],a[s])];
			cout << ans << endl;
		}
	}
	return 0;
}

P3854 [TJOI2008] 通讯网破坏

相当于在圆方树上询问是否第三个点是否在第一个点和第二个点的路径上 类比仓鼠那道题即可得解

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
const int N = 1e6 + 5;
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m , q , cnt , block , w[N] , ans , a[N];

int dfn[N] , low[N] , timer;
int sta[N] , tp;

vector<int> e[N] , ee[N];
inl void add ( int u , int v ) { e[u].eb(v); }
inl void adde ( int u , int v ) { ee[u].eb(v); }

void tarjan ( int u )
{
	dfn[u] = low[u] = ++timer;
	sta[++tp] = u;
	for ( auto v : e[u] )
		if ( !dfn[v] )
		{
			tarjan(v) , low[u] = min ( low[u] , low[v] );
			if ( low[v] >= dfn[u] )
			{
				++ block;
				while ( tp )
				{
					int x = sta[tp--];
					adde ( block , x ) , adde ( x , block ) , w[x] = 1;
					if ( x == v ) break;
				}
				adde ( block , u ) , adde ( u , block ) , w[u] = 1;
			}
		}
		else low[u] = min ( low[u] , dfn[v] );
}

int sz[N] , fa[N] , dep[N] , sum[N] , son[N];
int tim , rev[N] , pos[N] , top[N];

struct node 
{
	void dfs1 ( int u , int ff )
	{
		fa[u] = ff , sum[u] = sum[ff] + w[u] , dep[u] = dep[ff] + 1 , sz[u] = 1;
		for ( auto v : ee[u] )
			if ( v ^ ff )
			{
				dfs1 ( v , u );
				sz[u] += sz[v];
				if ( sz[son[u]] < sz[v] ) son[u] = v;
			}
	}
	void dfs2 ( int u , int tp )
	{
		top[u] = tp , pos[u] = ++ tim , rev[tim] = u;
		if ( son[u] ) dfs2 ( son[u] , tp );
		for ( auto v : ee[u] ) if ( v ^ fa[u] && v ^ son[u] ) dfs2 ( v , v );
	}
	int lca ( int u , int v )
	{
		while ( top[u] != top[v] )
		{
			if ( dep[top[u]] < dep[top[v]] ) swap ( u , v );
			u = fa[top[u]];
		}
		if ( dep[u] < dep[v] ) swap ( u , v );
		return v;
	}
}L;

int dis ( int u , int v )
{
	int lcaa = L.lca ( u , v );
	return dep[u] +	dep[v] - 2 * dep[lcaa];
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read() , block = n;
	for ( int i = 1 , u , v ; i <= m ; i ++ ) u = read() , v = read() , add ( u , v ) , add ( v , u );
	for ( int i = 1 ; i <= n ; i ++ ) if ( !dfn[i] ) tarjan(i);
	L.dfs1 ( 1 , 0 ) , L.dfs2 ( 1 , 1 );
	q = read();	
	for ( int i = 1 ; i <= q ; i ++ )
	{
		int s = read() , t = read() , c = read();
		if ( dis ( s , t ) == dis ( s , c ) + dis ( c , t ) ) puts("yes");
		else puts("no");
	}
	return 0;
}
posted @   Echo_Long  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示