圖論

Overview

tree

DFS tree

強連通分量 (SCC)

雙連通分量 (BCC)

2-sat

圖論雜題

flow (不會講)

講不完的話可以參考的東西

去年校隊培訓簡報

flow: 一樣是去年校隊培訓簡報

樹 DP

幾個可以做的東西

size

子樹和

...

還是可以做的東西

樹的最小支配集:點集中取出儘量少的點,使剩下的點與取出來的點都有邊相連

 

樹的最小點覆蓋:點集中取出儘量少的點,使得所有邊都與選出的點相連

 

 樹的最大獨立集:點集中取出儘量多的點,使得這些點兩兩之間沒有邊相連

 

by cheissmart

Greedy!

等等主題不是樹 DP 嗎

Greedy 也是一種 DP

改成帶權呢

dp[v][1] 表示當前的點一定選的最大值

dp[v][0] 表示一定不選的最大值

 

$$dp[v][1] = \sum_{u \rightarrow v} dp[u][0] + a[v]$$

$$dp[v][0] = \sum_{u \rightarrow v} max(dp[u][0], dp[u][1])$$

以最大獨立集為例

換根 DP

習題

樹上拓樸排序

對於每條被刪的邊,兩端點在被刪掉時的奇偶性會相同,兩端點被刪掉時都是奇數的叫做奇邊,偶數叫做偶邊

假設一個點的 deg 是 d

不管 d 是奇數或是偶數,刪除序列會是

... -> 偶邊 -> 奇邊 -> 偶邊 -> 奇邊

=> 我們可以看出來點往父親的邊是奇邊還是偶邊 (或無解)

假設我們知道每條邊是奇邊還是偶邊了,那麼對於一個點,我們只要確保所有經過這個點的邊的順序是對的就好了

把奇邊跟偶邊分別隨便排,然後建邊 -> 邊的圖

a -> b 代表 a 必須排在 b 的前面,可以發現他是 acyclic 的

最後跑一遍拓樸排序就好了

習題

說句笑話,打比賽記得按 rated

SCC

一個有向圖是 SCC iff 任意兩點皆能互相到達

Tarjan!

 

你們以後會一直看到他

Tarjan

考慮 dfs tree

對於每個點維護 dfn 表示 dfs 序

low 表示從當前的點的子樹可以走到的最小 dfs 序的點

dfn == low <=> 是 SCC 中第一個遍歷到的點

證明

dfn != low 代表從他開始可以走到已經走過的點 -> 可以往上走 -> SCC 裡面有人比他更早遍歷過

實作

用 stack 維護當前的 dfs 序跟哪些點還沒有走過

在判 low 的時候要注意要記得判是不是已知這個點的 SCC,如果是的話就不要更新 low

Code

vector<vector<int> > e;
vector<int> dfn,low,tt;
stack<int> s;
int cnt=1,cc=0;
void dfs(int u){
	dfn[u]=low[u]=cnt;
	cnt++;
	s.push(u);
	for(auto v:e[u]){
		if(!tt[v]){
			if(dfn[v]){
				low[u]=min(low[u],dfn[v]);
			}
			else{
				dfs(v);
				low[u]=min(low[u],low[v]);
			}
		}
	}
	if(dfn[u]==low[u]){
		cc++;
		while(u!=s.top()){
			tt[s.top()]=cc;
			s.pop();
		}
		tt[u]=cc;
		s.pop();
	}
}

//tt[] = SCC

題目

我不會,自己看

BCC (Edge)

一條邊是 bridge 代表把他拔掉之後的連通塊數量會增加

一張圖是 BCC 代表他連通並且沒有橋

在此討論無向圖

同樣考慮 dfs tree
可以發現 back edge 不會是 bridge
=> 只要考慮 tree edge

可以發現一條 tree edge 是 bridge 的充要條件是 low[下面那個點] = dfn[下面那個點]

維護 dfn 跟 low 陣列

Extra: 找到每個點分別在哪個 BCC 裡面

用 stack!

vector<vector<int> > e;
vector<int> dfn,low;
vector<pair<int,int> > bridge;
stack<int> s;
int cnt=1,cc=0;
void dfs(int u,int f){
	dfn[u]=low[u]=cnt;
	cnt++;
	s.push(u);
	for(auto v:e[u]){
		if(v==f){
			continue;
		}
		if(dfn[v]){
			low[u]=min(low[u],dfn[v]);
		}
		else{
			dfs(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]==dfn[v]){
				bridge.push_back({u,v});
			}
		}
	}
}

Code

BCC (Vertex)

一個點是割點代表拔掉他之後的連通塊數量會增加

一張圖是 BCC (Vertex) 代表連通且沒有割點

 

一樣只考慮無向圖

一樣是 dfs tree 上面維護 dfn 跟 low

對於一個點 v 是割點

  1. 如果 v 不是根,代表說他有一個小孩 u 使得 low[u] >= dfn[v]
  2. 如果 v 是根,代表小孩個數 >= 2
vector<vector<int> > e;
vector<int> dfn,low;
vector<pair<int,int> > bridge;
stack<int> s;
int cnt=1,cc=0;
void dfs(int u,int f=-1){
	dfn[u]=low[u]=cnt;
	cnt++;
	s.push(u);
	int ch=0;
	for(auto v:e[u]){
		if(v==f){
			continue;
		}
		if(dfn[v]){
			low[u]=min(low[u],dfn[v]);
		}
		else{
			dfs(v,u);
			low[u]=min(low[u],low[v]);
			if(f!=-1 && low[v]>=dfn[u]){
				IS_CUTPOINT(v);
			}
			ch++;
		}
	}
	if(f==-1 && ch>=2){
		IS_CUTPOINT(v);
	}
}

Code

題目

2-SAT

你有 n 個條件,每個條件是形如 \( ( A \lor B )\) 的形式,問你有沒有辦法透過決定變數的值使每個條件皆成立

對於每個變數建 \(A\) 與 \(\neg A\) 兩個點

對於每個條件 \( A \lor B \) 建 \( \neg A \rightarrow B \) 與 \( \neg B \rightarrow A \) 兩條邊,代表 A 不成立的話 B 就必須成立,另一邊也是這樣

來建圖吧

可以發現從一個 A 開始,能走到的點都會必須成立

代表說只要 A 能走到反 A 且 反 A 能走到 A
整個邏輯式就無解

判兩個點是不是互相到達 -> SCC!

跑一次 tarjan,求出是不是有 A 跟反 A 在同一個 SCC 裡面,有的話就輸出無解

否則剩下的圖一定是 DAG -> 拓樸排序

假設 A 的拓樸排序在 反 A 前面 -> 反 A 一定不能走到 A

所以要輸出每個變數的值的話就是看 A 跟反 A 哪個比較後面就好

Code 跟模板題

#include <bits/stdc++.h>
#define fastio cin.tie(0);ios_base::sync_with_stdio(0);
#define fs first
#define sc second
using namespace std;
 
vector<vector<int> > e;
vector<set<int> > f;
vector<int> dfn,low,tt,y;
stack<int> s;
int cnt=1,cc=0;
void dfs(int u){
	dfn[u]=low[u]=cnt;
	cnt++;
	s.push(u);
	int j=e[u].size();
	for(int i=0;i<j;i++){
		if(!tt[e[u][i]]){
			if(dfn[e[u][i]]){
				low[u]=min(low[u],dfn[e[u][i]]);
			}
			else{
				dfs(e[u][i]);
				low[u]=min(low[u],low[e[u][i]]);
			}
		}
	}
	if(dfn[u]==low[u]){
		cc++;
		while(u!=s.top()){
			tt[s.top()]=cc;
			s.pop();
		}
		tt[u]=cc;
		s.pop();
	}
}
int main(){
	fastio;
	int n,m;
	cin >> m >> n;
	n*=2;
	e.resize(n);
	dfn.resize(n);
	low.resize(n);
	tt.resize(n);
	for(int i=0;i<m;i++){
		char a,b;
		int c,d;
		cin >> a >> c >> b >> d;
		c--;
		d--;
		if(a=='-'){
			c=n-c-1;
		}
		if(b=='-'){
			d=n-d-1;
		}
		e[n-c-1].push_back(d);
		e[n-d-1].push_back(c);
	}
	for(int i=0;i<n;i++){
		if(!tt[i]){
			dfs(i);
		}	
	}
	vector<int> in(cc);
	for(int i=0;i<n/2;i++){
		if(tt[i]==tt[n-i-1]){
			cout << "IMPOSSIBLE\n";
			return 0;
		}
	}
	for(int i=0;i<n;i++){
		tt[i]--;
	}
	f.resize(cc);
	for(int i=0;i<n;i++){
		for(auto k:e[i]){
			if(tt[i]==tt[k]){
				continue;
			}
			if(f[tt[i]].find(tt[k])==f[tt[i]].end()){
				f[tt[i]].insert(tt[k]);
				in[tt[k]]++;
			}
		}
	}
	queue<int> q;
	y.resize(cc);
	int z=0;
	for(int i=0;i<cc;i++){
		if(in[i]==0){
			q.push(i);
			y[i]=z;
			z++;
		}
	}
	while(!q.empty()){
		int x=q.front();
		q.pop();
		for(auto k:f[x]){
			in[k]--;
			if(in[k]==0){
				q.push(k);
				y[k]=z;
				z++;
			}
		}
	}
	for(int i=0;i<n/2;i++){
		if(y[tt[i]]>y[tt[n-i-1]]){
			cout << "+ ";
		}
		else{
			cout << "- ";
		}
	}
	cout << "\n";
	return 0;
}

題目

所以來做題吧

DSU

再說句笑話,打 continue 前記得輸出答案

我 vir 這場的時候這題就差一個忘記輸出答案就從 0 分變 100 分了

也許多一個資料結構(?

再多一點點東西(?

Cycle Basis

要怎麼表示一個圖的所有環

Made with Slides.com