圖論
Overview
tree
DFS tree
強連通分量 (SCC)
雙連通分量 (BCC)
2-sat
圖論雜題
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 是割點
- 如果 v 不是根,代表說他有一個小孩 u 使得 low[u] >= dfn[v]
- 如果 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
要怎麼表示一個圖的所有環
graph theory
By alvingogo
graph theory
- 750