圖論
Outline
- 圖介紹
- BFS/DFS
- 歐拉路徑
- 樹的介紹
- 樹上最遠距離
- DAG (有向無環圖)
- DSU
- 樹壓平 、 LCA
- 最小生成樹
- 最短路
- 二分圖匹配
圖的介紹


圖的種類
- 稀疏圖
- $$|V| \approx |E|$$
- 稠密圖
- $$E| \approx |V|^2$$
- 無向圖
- 無向邊所構成的圖
- 有向圖
- 有向邊所構成的圖
- 有向無環圖 (DAG)
- 有向邊 + 無環

圖的種類
- 完全圖
- 每個點對間都有邊相連
- 二分圖
- 可把圖分成恰兩部分,使得兩部分內部無邊相連、邊只會連接兩部分


圖的儲存
Edge List
- 就按照題目要求,用一個陣列把所有邊存下來
- 缺點:
- 每條邊之間沒有關聯性,在做圖的走訪時每次要重新枚舉整個陣列
鄰接矩陣
- 建一個 \(n \times n\) 的陣列, \(e[i][j]\) 存從 \(i \rightarrow \ j\) 的邊
- 優點:
- 有表示到節點和邊的關係
- 缺點:
- 這樣空間複雜度為 \(O(n^2)\) , 在稀疏圖太慢
鄰接陣列
- 改善鄰接矩陣,因為稀疏圖實際上邊數很少
- 所以改成開 \(n\) 個 vector 去存每個點 \(i\) 連出去的邊
vector<int> v[N];
vector<pair<int,int> > e[N];
void add_edge(int a,int b){
v[a].eb(b);
}
void add_edge(int a,int b,int w) {
e[a].eb(mp(b,w));
}圖的遍歷
深度優先搜索 (DFS)
- 和人走路相同,
- 會持續從一個點往下走直到沒路可走
- 實作時通常直接用遞迴

void dfs(int x){
vis[x]=true;
for(int i:v[x]){
if(!vis[i]) dfs(i);
}
}廣度優先搜索 (BFS)
- 他是再一個點時往他所有相鄰的點走
- 實作通常是使用 queue

void bfs(int s){
queue<int> q;
q.push(s);
while(!q.empty()){
int x=q.front();q.pop();
for(int i:v[x]) {
if(!vis[i]){
vis[i]=true;
q.push(i);
}
}
}
}- 判斷給定的圖是不是二分圖
- 二分圖定義是可以分成兩堆內部無邊的點集
- 可以把點上色,這樣只要有邊的顏色必定要不同
- DFS、BFS都可做
歐拉路徑
歐拉路徑
- 也稱作一筆畫問題
- 問是否可以在不走重複邊的情況下走完所有邊
- 並構造一組解
歐拉路徑
- 有向圖歐拉路徑存在條件
- 除起終點外其餘點的入度=出度,起點出度多1,終點入度多1
- 無向圖歐拉路徑存在條件
- 至多兩個奇點(度數為奇數)(其實只有 0/2)
- 0個奇點稱為歐拉迴路
歐拉路徑
- 對邊 DFS
- 實作上還是點枚舉
- 離開一條邊時加入答案
- 最終再把答案反轉
歐拉路徑
- 可注意到因為是對邊 DFS
- 因此一條邊遍歷後要從該點的陣列刪除
- 或是可以維護一個標記 \(in[i]\) 代表節點 \(i\) 已經枚舉到哪
- 求字典序最小的歐拉路徑
- 求字典序最小的歐拉路徑
- 只要DFS 時把邊按照字典序排好即可
大家來找碴


wiwihorz 讀書會圖論I簡報的 code
大家來找碴

wiwihorz 讀書會圖論I簡報的 code
- 前面有說過必須避免重複遍歷已走過的邊
- 若遇到恰兩個點互連爆多邊就會噴到 \(O(nm)\)
void dfs(int x){
for(int &p=it[x];p<SZ(e[x]);p++){
if(!used[e[x][p].S]) {
used[e[x][p].S]=1;
dfs(e[x][p].F);
}
}
ans.eb(x);
}一種正確寫法
樹
樹
- 圖論中的一種特例
- 由恰 \(n-1\) 條無向邊所組成的聯通圖

樹的遍歷
- 通常用 DFS
- 不同根節點會建出不同樹
- 可以注意到一個點會恰有一個祖先
- 其他為子節點
- 所以DFS 只需判斷該點是不是祖先
void dfs(int x,int p=-1,int d=0){
dep[x]=d;
for(int i:v[x]){
if(i==p) continue;
dfs(i,x,d+1);
}
}b967. 第 4 題 血緣關係 (樹直徑)
- 求樹上最遠兩點的距離
- 因為最遠距離必定有一端在根節點的最遠點
- 所以做兩次 DFS
- 第一次找到與根節點最遠的點
- 再從該點出發
b967. 第 4 題 血緣關係 (樹DP)
- 可以注意到對 \(x\) 子樹來說距離只有兩種情況
- 通過\(x\) 的
- 橫跨\(x\)某一子樹
- 維護 \(dis[x] , maxdep[x]\) 分別代表 \(x\) 內的最遠距離、距離 \(x\) 的最遠點(最深)
- \(dis[x]=\max_{c_1,c_2 \in x}(dis[x],maxdep[c_1]+max_dep[c_2]+2\)
b967. 第 4 題 血緣關係 (樹DP)
- 實作時可以維護目前為止的 \(maxdep[c_1]\)
- 所以不需同時枚舉點對 \(c_1,c_2\)
int dis[N],maxdep[x];
void dfs(int x,int p=-1){
maxdep[x]=dis[x]=0;
for(int i:v[x]){
if(i!=p){
dfs(i,x);
dis[x]=max(dis[x],max(dis[i],maxdep[x]+maxdep[i]+1));
maxdep[x]=max(maxdep[x],maxdep[i]+1);
}
}
}有向無環圖 (DAG)
有向無環圖
- 每條邊有方向、沒有出現環

拓撲排序
- 要怎麼好的遍歷 DAG
- 走訪一個點之前指向他的邊一定要先走訪
- 因為保證有向無環,必定有點可以先走訪
- 沒有被任何人指到的可以先走訪
- 拔掉該點後一樣是 DAG
拓撲排序

void toplogical sort(){
queue<int> q;
for(int i=1;i<=n;i++) if(!deg[i]) q.push(i);
while(q.size()){
int x=q.front();
q.pop();
for(auto [i,w]:v[x]){
deg[i]--;
if(!deg[i]) q.push(i);
}
}
}DAG dp
a454. TOI2010 第二題:專案時程
-
每個任務 \(x\) 有其花費天數、也有一些順序關係。他能開始做只有當連向 \(x\) 的所有任務都完成了

- 令 \(dp[x]\)代表 \(x\) 完成的天數
- \(dp[x]=max\{dp[i]+w\}(i,x,w)∈edge\)
- 按照拓撲排序順序轉移就好
void toplogical sort(){
queue<int> q;
for(int i=1;i<=n;i++) if(!deg[i]) q.push(i);
while(q.size()){
int x=q.front();
q.pop();
for(auto [i,w]:v[x]){
dp[i]=max(dp[i],dp[x]+w);
deg[i]--;
if(!deg[i]) q.push(i);
}
}
}並查集
並查集
-
一種資料結構可以進行兩種操作
- 詢問元素所在的集合
- 把兩集合合併
這裡的集合在圖論上被稱為連通塊
- 朋友的朋友也是朋友
給定\(M\)筆朋友關係,和\(Q\)筆詢問,問\(A,B\)是不是朋友。
並查集
- 把連通關係當作一顆樹,根節點就是最高層的祖先
- 同一棵樹代表在同一連通塊
- 初始每個人都是一棵樹
- 合併時就等價兩棵樹合併
並查集


並查集
- 既然是兩顆樹合併,直接拿根節點合併即可
- 有兩個優化
- 路徑壓縮
- 啟發式合併
- 用其中一個單次時間複雜度變成 \(O(\log{n})\)
- 兩者都用變成 \(O(\alpha(n)) \sim O(1)\)
並查集
- 路徑壓縮
- 因為你只在乎誰是樹根,尋找時也是要找樹根
- 找到後直接把尋找路段壓縮起來


並查集
- 啟發式合併
- 兩樹合併時用小的指向大的
- 也可以用樹高低的指向大的


並查集
void init(int n){
for(int i=1;i<=n;i++) p[i]=i,sz[i]=1;
}
int fp(int x){
if(x!=p[x]) p[x]=fp(p[x]);//路徑壓縮
return p[x];
}
void Union(int a,int b){
a=fp(a);//找到連通塊的祖先
b=fp(b);
if(a!=b){
if(sz[a]<sz[b]){
swap(a,b);//確保a是數量較大的
}
p[b]=a;//把b指向a
sz[a]+=sz[b];
}
}- 一樣有 CF EDU 教學和習題可練
樹壓平
樹壓平
- 把樹變成序列,維護進入和離開時間戳記
- \(in_i,out_i\)
- 有以下性質
- \([in_x,out_x]\) 表示 \(x\) 子樹
樹壓平

- 時間序列: [1,2,6,6,5,5,2,4,4,3,3,1]
int in[N],out[N];
int t=1;
void dfs(int x,int p=-1){
in[x]=t++; // 進入的時間戳記
for(int i:v[x]) {
if(i!=p) dfs(i,x);
}
out[x]=t++; // 離開的時間戳記
}樹壓平
- 有了時間序列就可以判祖孫關係
- 剛剛提到在 \([in[x],out[x]\) 內出現的是 \(x\) 子樹,
- 也就是 \(x\) 的子孫
bool isanc(int a,int b){
return in[a]<=in[b]&&out[b]<=out[a];
}最低共同祖先(LCA)
LCA
- \(O(\log{n})\) 求任意兩點\(a,b\)在樹上的最小共同祖先
- \(LCA(5,6)=2\)
- \(LCA(3,6)=1\)

LCA
- 假設 \(LCA(a,b)=w\)
- 那 \(w\) 會是 \(a\) 的 \(d_a\) 層祖先
- 對 \(a\) \(1 \sim d_a-1\) 都不是 \(b\) 祖先
- \(d_a \sim \inf \) 都是 \(b\) 祖先
- 有二分搜性質
- 可以把 \(d_a\) 二進位分解
LCA 倍增法
- 既然要二進為分解,代表需要知道 \(2\) 的冪次層祖先
- 倍增
void build(){
for(int i=1;i<=K;i++){
for(int j=1;j<=n;j++) ac[i][j]=ac[i-1][ac[i-1][j]];
}
}LCA
- 二進位分解後就依序看每一個 \(bit\) 要是 \( 1/0 \)
int LCA(int a,int b){
if(isanc(a,b)) return a;
if(isanc(b,a)) return b;
for(int i=K;i>=0;i--){//跳到洽 $k_1-1$ 層的位置
if(!isanc(ac[i][a],b)) a=ac[i][a];
}
return ac[0][a];
}LCA
int in[N],out[N];
int t=1;
void dfs(int x,int p=-1){
in[x]=t++; // 進入的時間戳記
if(p!=-1) ac[0][x]=p;
else ac[0][x]=x; // 預處理父親
for(int i:v[x]) {
if(i!=p) dfs(i,x);
}
out[x]=t++; // 離開的時間戳記
}
void build(){
for(int i=1;i<=K;i++){
for(int j=1;j<=n;j++) ac[i][j]=ac[i-1][ac[i-1][j]];
}
}
bool isanc(int a,int b){//判斷 a 是否為 b 的祖先
return in[a]<=in[b]&&out[b]<=out[a];
}
int LCA(int a,int b){
if(isanc(a,b)) return a;
if(isanc(b,a)) return b;
for(int i=K;i>=0;i--){//跳到洽 $k_1-1$ 層的位置
if(!isanc(ac[i][a],b)) a=ac[i][a];
}
return ac[0][a];
}LCA
- 有了 \(LCA\) 可以幹嘛
- 樹上兩點距離會恰巧通過 \(LCA(a,b)\)
- \(dis(a,b)=dis(a,LCA(a,b))+dis(LCA(a,b),b)\)
- 可以透過建深度陣列求出距離
最小生成樹
最小生成樹
- 給一堆邊,求權重總和最小的樹
-
同時也會是權重最大值最小的樹
Kruskal’s algorithm
- 把邊按照權重由小到大排序
- 依序確認這條邊是否可加入(是否不形成環)
- 最終即可得到答案

Kruskal’s algorithm
- 把邊權由小到大排序
- 一條邊要加入等價會合併兩聯通塊
- 並查集
Kruskal’s algorithm
DSU s; //並查集
void Kruskal(vector<edge> e){
sort(e.begin(),e.end(),cmp);//按照邊權由小到大排
int sum=0;
for(edge ei:e){
if(!s.same(ei.a,ei.b)) s.union(ei.a,ei.b),sum+=ei.w;
}
}Kruskal’s algorithm
- 證明:
- 假設目前找到的為 \(T\) ,權重更小者為 \(T^*\)
- \(T^*\) 按照 Kruskal 加邊順序第一條\(T\) 沒有的邊 \(e\)
- 那考慮把這條邊加上去,那會形成還需要再拔一條邊 \(e^*\)
- 但因為是按照邊權由小到大,所以 \(w(e^*)<w(e) \)
- 故與假設矛盾
- 出現邊權最小的生成樹證明也相同
裏表次元 (Dimension) (校內賽pF)

裏表次元 (Dimension) (校內賽pF)
- 題目等價有一張圖和 \(Q\) 次詢問
- 每次詢問從 \(s \rightarrow t\) 經過的最大邊最小可能是多少
裏表次元 (Dimension) (校內賽pF)
- 給定的是圖,但可以簡化成樹
- 最小生成樹
- 之後題目剩下要怎麼快速求兩點路徑上最大邊
- 倍增、LCA
最短路
最短路
- Dijkistra
- Bellman-ford
- Floyd-Washall
- 給你 \(n\) 點 \(m\) 邊的正權圖
- 求 \(1\) 到所有點的最短距離
- \(1 \leq n \leq 10^5\)
- \(1 \leq m \leq 2 \cdot 10^5\)
- 考慮 BFS
- 當你 BFS 時依序更新路 ...
- 但問題是可能會重複 鬆弛 (後來才該走的路先走了)
- 鬆弛 (relax) : 對節點 \(v\) 更新他指向的所有節點 \(u\)
- 時間複雜度會噴到 \(O(nm)\)
- 但假如可以定一個走的順序就只用走一次
Dijkistra
- BFS 時的問題是可能後來反而距離較小
- 所以如果可以保證每次都走距離小的就好了
- queue \(\rightarrow\) priority_queue
- 按照當前到所有點的距離,每次走最小的更新
Dijkistra

Dijkistra
- 實作時和 BFS Code 大致相同,改成 priority_queue 就好
typedef pair<int,int> pii;
void dijkistra(int s,int t){
priority_queue<pii,vector<pii>,greater<pii>> pq;
pq.push(mp(0,s));
fill(dis,dis+N,INF);
dis[s]=0;
while(pq.size()){
pii now=pq.top();
pq.pop();
for(pii p2:v[now.S]){
if(dis[p2.F]>dis[now.S]+p2.S) {
dis[p2.F]=dis[now.S]+p2.S;
pq.push(mp(dis[p2.F],p2.F));
}
}
}
}Dijkistra
- 每條邊只會走一次、每個點也只會進入一次
- 時間複雜度是 \(O(|V|+|E|\log{|V|})\)
Dijkistra
- 那如果有負權呢 ?
- 允許重複入隊
typedef pair<int,int> pii;
void dijkistra(int s,int t){
priority_queue<pii,vector<pii>,greater<pii>> pq;
pq.push(mp(0,s));
fill(dis,dis+N,INF);
dis[s]=0;
while(pq.size()){
pii now=pq.top();
pq.pop();
if(dis[now.S]<now.F) continue; //允許重複入隊寫法
for(pii p2:v[now.S]){
if(dis[p2.F]>dis[now.S]+p2.S) {
dis[p2.F]=dis[now.S]+p2.S;
pq.push(mp(dis[p2.F],p2.F));
}
}
}
}Dijkistra

- 可判負環的時間複雜度不是好的
Bellman-Ford 算法
- relax 一次不夠,就 relax 很多次
- 一條最短路必定是至多走\(n-1\) 條邊
- 因此只要 \(n-1\) 次 relax 即可
- 時間複雜度為 \(O(nm)\)
SPFA 算法
- 可注意到不一定每次都要 relax 所有點
- 第 \(t-1\) 次沒被 relax , 第 \(t\) 次必定不會
- code 和前面重複入隊 Dijkistra 很像,只是又把 priority_queue \(\rightarrow\) queue
- 期望時間複雜度 \(O(|V|+|E|)\)
- 但最差可能和 Bellman-ford 一樣 \(O(nm)\)
但師大測資一定可以
SPFA 算法

wiwihorz 暑培圖論II簡報的 code
- 給 \(n\) 點 \(m\) 邊的有向有權圖
- 輸出 \(n\) 行,其中第 \(i\) 行包含一個整數,表示從節點 \(1\) 走到節點 \(i\) 的最短路徑長,如果無法走到 \(i\),輸出 QwQ;如果從節點 \(1\) 到 \(i\) 的路徑長可以任意小,輸出 OAO。
- 只要 \(n-1\) 次 relax 即可
- 因此只要第 \(n\) 次還被 relax 到,代表負環可走到該點
- 負環上的點也都會在 \(n\) 次被 relax
- 因此只要從這些點做 BFS 即可

wiwihorz 暑培圖論II簡報的 code

wiwihorz 暑培圖論II簡報的 code
- 給 \(n\) 點 \(m\) 邊的有向有權圖
- 求圖上任一個負環
- 前面講的是判有經過起始點的環的作法
- 唯一與起始點 \(s\) 相關是 \(dis[s]:=0\)
- 但這次只需要判負環,只在乎是否會 relax \(n\) 次
- 把 \(dis[s]\) 丟掉
- 一樣 relax \(n\) 次,找到與負環相通的點
- 從該點倒退走 \(n\) 次後,必在負環上
- 再從該點倒退走找負環
Floyd-Washall 算法
- 求全點對最短距離算法
- 其實是一種 \(dp\)
- 假設有經過點 \(k\)
- \(dis[l][r]=dis[l][k]+dis[k][r]\)
Floyd-Warshall 算法
- \(dis[l][r]=dis[l][k]+dis[k][r]\)
- 先枚舉轉移點 \(k\) , 內部再枚舉距離兩端
- 等價是做當轉移點限用 \(1 \sim k\) 情況下的最短距離
- 迴圈順序錯誤只要跑 3 次就會對
void FloydWarshall(){
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dis[i][j]=min(dis[i][k]+dis[k][j]);
}
}
}
}二分圖匹配
二分圖匹配
-
二分圖 (bipartite graph)
-
可以把點分成兩堆且同一堆內的節點間都沒有邊
-
-
匹配 (matching)
-
找一些點兩兩配對,每個點只能在一個匹配中且同一匹配的點必須相鄰
-
-
最大匹配 (maximum matching)
-
有最多配對的匹配
-
-
匹配是一個邊的獨立集
名詞介紹
-
匹配點 有被配對的點
-
匹配邊 兩端是一個配對的邊
-
交錯路徑
-
交替經過未匹配邊、匹配邊的路徑
-
-
增廣路
-
一種滿足頭尾都是未匹配點的交錯路徑
-
Berge's lemma

找二分圖最大匹配
- 根據 Berge's lemma 就一直找增廣路即可
- 把圖分成兩半 \(X,Y\)
- 枚舉 \(X\)的點 如果有找到增廣路則反轉邊,匹配數+1
- 時間複雜度 \(O(|V||E|)\)
找二分圖最大匹配

Code
const int N=1e3+7;
vector<int> v[N];//X的鄰點
int match[N]; // Y所匹配的節點
bool vis[N]; //在這次尋找中是否被走過
int dfs(int x){
for(int y:v[x]){
if(vis[y]) continue;
vis[y]=true;
if(match[y]==-1||dfs(match[y])){
match[y]=x;
return true;
}
}
return false;
}
int bipartiteMatching(int n){
fill(match,match+n+1,-1);
int ans=0;
rep(i,1,n){
fill(vis,vis+n+1,0);
if(dfs(i)) ans++;
}
return ans;
}
一些集合
-
點覆蓋 用點去覆蓋所有邊的點集合
-
點獨立集 互不相鄰的點集
-
邊覆蓋 用邊去覆蓋所有點的邊集合
-
邊獨立集 互不共點的邊集 ( 匹配 )
符號
-
二分圖左右兩側 \(X,Y\)
-
最小點覆蓋 \(C_v\)
-
最小邊覆蓋 \(C_e\)
-
最大點獨立集 \(I\)
-
最大邊獨立集 = 最大匹配 = \(M\)
性質
- \(|C_v|=M\)
- \(|I|=|V|-|C_v|\)
- \(|M|=|V|-|C_e|\)
Kőnig’s theorem
因為覆蓋 \(M\) 條匹配至少需要 \(|M|\)個點
所以 \(|C_v| \geq |M|\)
所以只要構造出一組點獨立集 \(|C_v|=|M|\)即可
構造方法如下:
從 左邊沒有被匹配的點去走符合增廣路 "交替出現" 要求的路徑(未匹配邊-匹配邊-未匹配邊...) 並標記路過的所有點。
之後左邊沒被標記到的點 + 右邊被標記的點 即為最小點覆蓋
Kőnig’s theorem
證明- 1 ( 該得到的點集可以覆蓋所有的邊 ):
證明不可能有一條邊 左端有標記,右端沒標記
- 假設這條邊屬於匹配
- 則右端的標記必定是從左邊而來,故兩端都有或都沒有
- 假設這條邊不屬於匹配
- 則右端點可以從左端點走去,故兩者都會有標記
Kőnig’s theorem
假定得到點集為 \(|P|\)
證明-2 \(|P|=|M|\)
- 左邊未被標記點代表他是一條匹配邊左端(且該條匹配邊沒被標記過)
- 右邊被標記點也代表他是一條匹配邊右端
- 所以 \(|P|=|M|\)
Kőnig’s theorem
最大點獨立集
- 任意點獨立集的補集就是點覆蓋
- 可以想一下
- 最小點覆蓋的補集就是最大點獨立集
- \(I=V \setminus C_v\)
最大點獨立集
- 最小點覆蓋的補集就是最大點獨立集
- \(I=V \setminus C_v\)


最小邊覆蓋
首先圖不會有孤點,不然一定無解
1. 把 \(M\)中的邊都加入 \(C_e\) 則覆蓋\(2|M|\)個點,
最多再選\(V-2|M|\)條邊集可覆蓋所有點
故\(|C_e|\leq |V|-|M|\)
最小邊覆蓋
2. 因為 \(C_e\) 上若有環則至少可以拔掉一條邊,
故 \(C_e\) 為森林,連通塊數量為 \(|V|-|C_e|\)
則從每個連通塊選一條邊會形成邊獨立集
因為邊獨立集不會超過最大邊獨立集
所以連通塊數量必須小於最大邊獨立集(最大匹配)
故 \(|V|-|C_e|\leq |M|\)
最小邊覆蓋
由 1. ,2. 得證 \(|C_e|=|V|-|M|\)
例題
例題
- 把題目放在二分圖上思考,
- 每個皮皮\(x,y\)要被打掉代表 \(x\)被選或是\(y\)被選
- 最小點覆蓋
例題
- 最小點覆蓋構造方法
- 如前面證明時所使用的方法,就是枚舉 \(Y\) 中未被匹配的點,然後去找部分增廣路徑然後把經過的點標記
- 左邊被標記的點 + 右邊未被標記的點即為答案
- 實作方法:
- 把未匹配的邊改成右指向左
- 匹配邊為左指向右
- 之後就是 DFS
- 參考Code
構造Code
const int N=1e3+7;
vector<int> v[N];//X的鄰點
int match[N]; // Y所匹配的節點
bool vis[N]; //在這次尋找中是否被走過
int dfs(int x){
for(int y:v[x]){
if(vis[y]) continue;
vis[y]=true;
if(match[y]==-1||dfs(match[y])){
match[y]=x;
return true;
}
}
return false;
}
int bipartiteMatching(int n){
fill(match,match+n+1,-1);
int ans=0;
rep(i,1,n){
fill(vis,vis+n+1,0);
if(dfs(i)) ans++;
}
return ans;
}
vector<int> v2[2*N];
int tag[2*N];
vector<pii> e;
void paint(int x){
tag[x]=true;
for(int i:v2[x]){
if(!tag[i]){
paint(i);
}
}
}
void getce(int n){
int ans=bipartiteMatching(n);
cout<<ans<<"\n";
for(pii p2:e){
if(match[p2.S]!=p2.F){
v2[p2.S+n].eb(p2.F);
}
else v2[p2.F].eb(p2.S+n);
}
rep(i,1,n){
if(match[i]==-1){
paint(i+n);
}
}
rep(i,1,n){
if(tag[i]) cout<<1<<" "<<i<<"\n";
}
rep(i,n+1,2*n){
if(!tag[i]) cout<<2<<" "<<i-n<<"\n";
}
}例題
有一個棋盤,然後接下來會有 \(m\) 個事件發生,第 \(i\)個事件會在時間 \(t_i\) 發生在 (\(x_i\),\(y_i\)),你可以派一些人去處理這些事件。一個人被派出去的時候,他一開始可以在任何位置,接下來他移動到別的地方花的時間等於曼哈頓距離。求你至少要派幾個人。
\( m \leq 1000 \)
例題
對事件建點, \(i_{out},i_{in}\)
若從 \(i\) 事件結束後可以接著做 \(j\) 事件
則建邊 \(i_{out} \rightarrow j_{in}\)
等價於要用最少的路徑去使每個節點都被走訪
可以先假設答案是 \(m\)
那之後會發現如果有一個匹配 \(i,j)\) 則 答案會 -1。
故最佳解即為 \(|V|-|M|\)
練習
- TIOJ 2037 . 警力配置
-
GCJ 2021 R2 pD Retiling Test Set 1
- Test Set1 (權重為1)
題目講解
咖哩採買(Shopping) (校內賽pD)
- 給定一張有向帶權圖,問每條邊是否可能成為 \(1\) 走到 \(N\) 的最短路上的邊
- \( N,M \leq 5 \cdot 10^5\)
咖哩採買(Shopping) (校內賽pD)
- 求 \(1 \sim N\) 的最短路的值並不難 (Dijkstra就可以搞定)
圖論
By yuhung94
圖論
- 272