浙江金華 圖論整理


圖論

目錄

基礎知識儲備:

(1)、概念:

圖 G 是一個二元組(V,E),其中V稱為頂點集,E稱為邊集。它們亦可寫成 V(G)和E(G)。E的元素是一個二元組數對,用(x,y)表示,其中x,y∈V。

(2)、圖的儲存:

	①鄰接表  
	②鏈式前向星  
	③鄰接矩陣

(3)、度數序列:

①若把圖 G 所有頂點的度數排成一個序列 S,則稱 S 為圖 G 的度數序列。
②考慮無向圖,d1, d2, ... ,dn表示每個點的度數,d1 + d2 + ... + dn= 2e, 每條邊被計算兩次,一共有偶數個度數奇數的點。

各類算法簡介:

(1)、Havel–Hakimi算法簡介:

給定一串有限多個非負整數組成的序列,是否存在一個簡單圖使得其度數列恰為這個序列。令S=(d1,d2,...,dn)為有限多個非負整數組成的非遞增序列。 S可簡單圖化當且僅當有窮序列S’=(d2-1,d3-1,...,d(d1+1)-1,d(d1+2),...,dn)只含有非負整數且是可簡單圖化的。

(2)、Erdős–Gallai定理簡介:

令S=(d1,d2,...,dn)為有限多個非負整數組成的非遞增序列。S可簡單圖化當且僅當這些數字的和為偶數,並且對任意1<=k<=n都成立。
公式

(3)、遍歷(DFS/BFS)(pass...)

(4)、DFS Forest簡介:

Tree Edge指樹邊
Back Edge指連向祖先的邊
Forward Edge指連向子孫的邊
Cross Edge指連向樹其他分支的邊
在無向圖中只存在Tree Edge和Back 

(5)、歐拉回路

①簡介:

無向圖有歐拉回路的充要條件為每個點度數都是偶數且邊連通(注意孤立點)。有向圖有歐拉回路的充要條件為每個點的 入度 = 出度 且邊連通。一個聯通無向圖,有2k個奇數點,那么需要k條路徑。

②代碼實現:

inline void dfs(int u) {
	for(int i=head[u];i;i=head[u]) {
		while (i && vis[abs(s[i])]) i=e[i].nxt;
		head[u]=i;
		if(i) {
			vis[abs(s[i])]=1;
			dfs(v[i]);
			q[++top]=s[i];
		}
	}
}

(6)、最小生成樹

①Prim / Kruskal (復雜度: O(n^2) / O(m log m))

②Kruskal算法的正確性

擬陣(E,I)滿足I的每個元素為E的子集。
空集屬於I
如果A屬於I,那么A的所有子集也屬於I
如果A,B屬於I,並且|A|>|B|,那么存在一個A中不屬於B中的元素u,滿足B∪{u}也屬於I。
這樣就被稱為擬陣,對於擬陣,我們可以使用貪心算法從小到大或者從大到小選擇。
令E為邊集,I為所有生成森林的集合,那么(E,I)為擬陣。
常見的擬陣還有匹配擬陣和線性無關組。

③Borůvka算法簡介:

一開始每個連通分量是一個點本身。每輪每個連通分量選擇和其他連通分量相連的最小的邊,然后合並。時間復雜度O(E log V)

④最小瓶頸生成樹簡介:

使得生成樹樹上最大邊權值最小。
方法1: 最小生成樹一定是最小瓶頸生成樹。
方法2: 二分答案,看點是否連通。
類比找第k大值的方法,首先隨機一個邊權w。
然后將不超過這個邊權的邊加入,遍歷這張圖。
如果圖連通,那么瓶頸不超過w,於是只需考慮邊權不超過w的邊。
否則將這些連通點縮起來,考慮邊權大於w的邊。
每次將問題的規模縮小至一半。
期望時間復雜度O(m)。

⑤生成樹計數簡介:

1.Prufer序列:一棵樹要得到Prufer序列,方法是逐次去掉樹的頂點,直到剩下兩個頂點。考慮樹T,其頂點為{1, 2, ... ,n}。在第i步,去掉標號最小的葉子,並把Prufer序列的第i項設為這葉的鄰頂點的標號。一棵樹的序列明顯是唯一的,而且長為n − 2。

2.復原:設這Prufer序列長n − 2。首先寫出數1至n。找出1至n中沒有在序列中出現的最小數。把標號為這數的頂點和標號為序列首項的頂點連起來,並把這數從1至n中刪去,序列的首項也刪去。接着每一步以1至n中剩下的數和余下序列重復以上步驟。最后當序列用完,把1至n中最后剩下的兩數的頂點連起來。

3.Cayley定理:完全圖的生成樹個數為n(n-2)次。如果每個點的度數為di,那么生成樹個數為(n-2)!/(d1-1)!/(d2-1)!/.../(dn-1)!每個連通塊大小為ai,那么添加一些邊將這些連通塊連通的生成樹個數為a1*a2*...*an*(a1+a2+...+an)(n-2)次。

(7)、Matrix-Tree定理簡介:

	令G=D-A,然后去除G的任意一行一列G’,G’的行列式即生成樹個數。有向圖計數,即樹形圖個數。這里的D變為每個點的入度,刪去第i行第i列為從第i個點出發的樹形圖個數。

(8)、最短路(SSSP)

①簡介:

Dijkstra/Bellman Ford算法(時間復雜度O(m log n)或者O(nm))
Floyd算法(O(n^3))/ Johnson算法(負權)((nm log n))

①算法正確性:

Dijkstra 貪心
Bellman Ford 動態規划

②一些變種:

邊權是0/1
雙端隊列,如果是0在頭部插入,否則在尾部插入。
總長不超過W, 正權
使用0..W的桶+鏈表維護這些點,時間復雜度O(m+W)。

③最短路徑樹(圖):

(9)、差分約束系統簡介:

根據最短路有不等式dis(v)<=dis(u)+w(u,v),恰好存在一個這樣的u滿足條件。並且這樣計算出來的dis(v)是最大的。,對於一些s(v)<=s(u)+w(u,v)的限制,可以類比最短路建圖。

判斷解唯一時,對原圖求一遍最短路。將原圖取反,邊權取反,求一遍最長路。一個標號對應的是能取到的最小值,一個是最大值。如果相同則解唯一。

(10)、Johnson算法介紹簡介:

首先給圖中每個點一個標號h(v), 把每條邊(u,v)邊權改成w(u,v)+h(u)-h(v)。對於s-t的一條路徑p,權值為


所以不會改變最短路。
從1號點出發跑一遍最短路,記h(v)=dis(v)。
由不等式可以得到dis(u)+w(u,v)>=dis(v),也就是改完之后邊權非負。
之后可以每個點用Dijkstra跑。

(11)、半徑/直徑 (正權圖)簡介:

u的偏心距:ecc(u)=max dis(u,v)
直徑 d=max ecc(u)
半徑 r=min ecc(u) (d≠2r)
中心 arg min ecc(u) (要求定義在點上)
絕對中心 (可以定義在邊上)

(12)、絕對中心 && 最小直徑生成樹

①絕對中心簡介:

固定一條邊(u,v),考慮上面的點p的偏心距。
假設第三個點是w, dis(p,u)=x
那么對應的折線為 min(x+dis(u,w), w(u,v)-x+dis(v,w))。
那么偏心距為n條折線的最大值形成的折線。
按左端點排序維護一下。
時間復雜度O(nm log n)

②最小直徑生成樹簡介:

絕對中心的最短路樹證明:

注意一棵樹T有直徑為半徑的兩倍(對絕對中心來說)。
如果最小直徑生成樹T’不包含絕對中心,那么取T’的絕對中心v,顯然矛盾。

(13)、拓撲排序

每次去掉圖中入度為0的點。時間復雜度O(n+m)。如果最后不為空集那么這個圖不為DAG。 否則每個點入度不為0,即每個點可以選擇一個前趨,沿着前趨走根據抽屜原理一定能找到相同點,也就是一個環。

①字典序最小的拓撲序

每個點有不同的標號,要使得拓撲序最小。將拓撲排序的隊列改成優先隊列即可。

②最小拓撲序的一個變種

使得最后的拓撲序中1的位置盡量靠前,如果相同比較2的位置,依次類推。首先考慮如何求1最早出現的位置,可以將原圖反向,然后每次彈除了1之外的元素,直到隊列只剩下1為止。這是反圖中1的最晚的出現的位置,也就是原圖中最早的。根據是否在隊列里,這個圖被分成兩部分,在對應的圖中用同樣的方法處理2,依次類推。容易發現每次找盡量大的元素出隊,能完成上述的過程。所以等價於反圖最大字典序。

(14)、Hall’s marriage theorem簡介:

對於一個二分圖G=(X,Y,E),記S為X的一個子集,N(S)為所有S中所有點鄰居的並集。
一個圖有完備匹配當且僅當X的所有子集S都有|S|<=|N(S)|
對一般圖的推廣:

推論: 每個正則二分圖都有完備匹配。

(15)、Kőnig's theorem簡介:

最小點覆蓋=最大匹配 (與最大流最小割定理等價)
最大獨立集=點數-最大匹配 (獨立集為點覆蓋的補集)
最小邊覆蓋=最大獨立集 (獨立集中每個點需要一條邊去覆蓋)

(16)、DAG最小路徑覆蓋簡介:

覆蓋所有的邊: 每條邊下界設為1, 然后求最小流。
覆蓋所有的點: 建立二分圖,對於u->v的邊,看做二分圖中的(u,v’),然后答案為點數-最大匹配。
Dilworth 定理: 最大反鏈=最小鏈覆蓋

(17)、強連通分量 && 雙聯通分量

①強連通分量簡介:

Tarjan:
首先每個點根據DFS的時候訪問的順序進行標號,記作這個點的時間戳。
然后每個點維護一個low值,即這個點通過Tree edge和Back edge能訪問到時間戳最小的點。
如果一個點的能訪問到最早的點為這個點,就會形成一個新的強連通分量。
一個圖將強聯通分量縮起來將會形成一個DAG。

②代碼(tarjan):

void tarjan(int pos){
    vis[stack[++index]=pos]=1;//入棧並標記
    LOW[pos]=DFN[pos]=++dfs_num;
    for(int i=pre[pos];i;i=E[i].next){
        if(!DFN[E[i].to]){
            tarjan(E[i].to);
            LOW[pos]=min(LOW[pos],LOW[E[i].to]);
        }
        else if(vis[E[i].to]) LOW[pos]=min(LOW[pos],DFN[E[i].to]);
    }
    if(LOW[pos]==DFN[pos]){
        vis[pos]=0;
        size[dye[pos]=++CN]++;//染色及記錄強連通分量大小
        while(pos!=stack[index]){
            vis[stack[index]]=0;
            size[CN]++;//記錄大小
            dye[stack[index--]]=CN;//彈棧並染色
        }
        index--;
    }
}

③雙聯通分量簡介:

點連通度: 最小的點集使得刪去之后圖不連通
邊連通度: 最小的邊集使得刪去之后圖不連通
如果一個圖的點連通度大於1,那么是點雙連通的,邊連通同理。
雙聯通分量為圖中的極大雙聯通子圖。

(18)、割點和橋

①簡介:

考慮DFS樹,每條非樹邊對應着一個點到祖先的路徑。對於一條非樹邊只要把對應的邊打上標記即可。比如對於(u,v)這條非樹邊,只要在u點打上+1的標記,v點打上-1的標記。v到v的父親的樹邊的覆蓋次數為子樹內所有標記的和。割點同理(注意特判根節點和葉節點)。

注意打標記這個過程可以在線完成。可以使用一個並查集維護當前雙聯通分量中的點,記錄一下每個雙聯通分量中最高的點。然后對於一條非樹邊,暴力將這些點合並起來即可。因為一條邊最多被合並一次,需要不超過O(m)次的並查集操作。邊雙聯通分量縮完之后會形成一棵樹。

②例(2-SAT):

一堆變量的二元限制,問是否存在合法的賦值。

首先每個變量拆兩個點,Xi和Xi’表示Xi=1或0對於Xi or Xj這樣的限制,從Xi’向Xj連邊,從Xj’向Xi連邊,表示如果Xi取0,那么Xj要取1,反之亦然。同時對於Xi=1這樣的限制可以轉化為Xi or Xi,於是從Xi’向Xi連邊,表示不能取Xi’。對於這樣的圖求強連通分量。有解的充要條件為對於每個變量Xi和Xi’不在同一個強連通分量里。求方案的時候,對於一個變量Xi和Xi’,只要取Tarjan算法中強連通分量早形成的即可。感性認識: 如果Xi能到達Xi’,那么Xi’的強連通分量會早形成。

(19)、圖論例題整理(未完...):

Allowed Letters(CF 1009 G)代碼實現:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5+10;
const int M=63;

int m,x,len,l,t;
int a[N],ans[N],js[7],f[M+2][N];
char s[N],ch[9];

inline int read() {
	int n=0,f=1;char ch=getchar();
	while (ch<'0' || ch>'9') {if(ch=='-') f=-1;ch=getchar();}
	while (ch<='9' && ch>='0') {n=(n<<3)+(n<<1)+ch-'0';ch=getchar();}
	return n*f;
}

inline int work(int x) {
    for(int k=0;k<=M;++k) {
        int jc=0;
        for(int i=0;i<6;++i) 
            if((k>>i)&1) jc+=js[i];
        if(f[k][len]-f[k][x]<jc) return 0;
    }
    return 1;
}

int main() {
	scanf("%s%d",s+1,&m);
    len=strlen(s+1);
    //預處理一下 
    for(int i=1;i<=len;++i) {
        a[i]=M,ans[i]=-1;
        ++js[s[i]-'a'];
    }
    for(int i=1;i<=m;++i) {
        scanf("%d%s",&x,ch);
        l=strlen(ch),t=0;
        for(int j=0;j<l;++j) t|=1<<(ch[j]-'a');
        a[x]&=t;
    }
	for(int k=0;k<=M;++k) 
        for(int i=1;i<=len;++i) f[k][i]=f[k][i-1]+(bool)(a[i]&k);
    for(int i=1;i<=len;++i) {
        for(int j=0;j<6;++j) {
            --js[j];
            if(((a[i]>>j)&1) && work(i)) {
                ans[i]=j; 
				break;
            }
            ++js[j];
        }
        if(ans[i]==-1) {
            printf("Impossible\n");
            return 0;
        }
    }
    for(int i=1;i<=len;++i) cout<<char(ans[i]+'a');
    return 0;
}

Revmatching (TCO 2015 1A Hard)

Valid BFS? (CF 1037 D)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
const int N=2e5+10;

int n,x,y,res;
int vis[N],head[N];
vector<int> g[N],ans;
queue<int> q;

struct node {
	int a,b,nxt;
}e[N];

inline int read() {
	int n=0,f=1;char ch=getchar();
	while (ch<'0' || ch>'9') {if(ch=='-') f=-1;ch=getchar();}
	while (ch<='9' && ch>='0') {n=(n<<3)+(n<<1)+ch-'0';ch=getchar();}
	return n*f;
}

inline int cmp(int x,int y) {
    return e[x].b<e[y].b;
}

inline void init() {
    for(int i=1;i<=n;++i) e[e[i].a].b=i;
    for(int i=1;i<=n;++i) {
        sort(g[i].begin(),g[i].end(),cmp);
        for(int j=0;j<g[i].size();++j) res=g[i][j];
    }
}

inline void bfs(int s) {
    q.push(s);
    vis[s]=1;
    while (!q.empty()) {
        int u=q.front();
        q.pop();
        ans.push_back(u);
        for(int i=0;i<g[u].size();++i) {
            int v=g[u][i];
            if(vis[v]) continue;
            vis[v]=1;
            q.push(v);
        }
    }
}

inline int pd() {
    for(int i=0;i<ans.size();++i) 
        if(e[i+1].a!=ans[i]) return 0;
    return 1;
}

int main() {
	n=read();
    for(int i=1;i<=n-1;++i) {
    	x=read(),y=read();
        g[x].push_back(y);
        g[y].push_back(x);
    }
    for(int i=1;i<=n;++i) e[i].a=read();
    if(e[1].a!=1) {
        printf("No");
        return 0;
    }
    init();
    bfs(1);
    if(pd()) printf("Yes");
    else printf("No");
	return 0;
}

Cycle (HDU 5215)

航空管制 (NOI 2010)

題目簡述:

有n個航班依次起飛,一個時刻只能有一個飛機起飛,並且有m個限制:第一種限制,第i個飛機必須在c(i)的時刻前起飛。第二種限制,第i個飛機必須在第j個飛機之前起飛。

詢問:
一個可行的起飛方案。每個飛機最早的起飛時間。n<=2e3, m<=1e4

Solution

	倒過來變成每個飛機在某個時刻之后可以起飛。
	第二問變成每個飛機最晚什么時候起飛。
	直接用拓撲排序的做法即可。

ABland Yard (AGC 27 C)

題目簡述:

給你一個有向圖,每個點都標有01。問是否對於所有01串,都存在一條路徑,使得將路徑上經過的點的數字連起來得到01串。

Solution

	這里只討論二分圖。
	最大匹配: Hungarian/Hopcroft-Karp/Dinic
	最大權匹配: KM/費用流
	判斷是否存在奇環,只要看是不是二分圖即可。
	判斷是否存在偶環,首先看每條非樹邊對應的環是不是偶環。
	如果存在那么就找到了偶環。
	否則考慮如果兩個奇環相交,那么去除中間部分就會形成一個偶環。
	所以對於奇環的非樹邊只要暴力訪問樹邊打上標記,如果已經有標記了就說明存在奇環。
	時間復雜度O(n+m)

Cycling City (CF 295 E)

題目簡述:

你有一個n個點m條邊的無向圖。(n, m<=2e5)
問是否存在兩個點,使得這兩個點之間有三條簡單路,並且這三條簡單路沒有公共點。

Solution

	如果兩條非樹邊對應的環有交,那么一定可以找到這樣的兩個點。否則不存在。

Hangar Hurdles (CERC 16)

題目簡述:

有一個n*n的網格圖,上面有些格子可行,有些格子是障礙。(N<=1000 Q<=300000 )
有Q個詢問,想把一個正方形箱子從(r1,c1)推到(r2,c2),問箱子最大的大小。(起點終點是正方形箱子的中點)

Solution

	首先從障礙開始bfs,求出每個格子最近的障礙。然后變成了求一條路徑,使得路徑上的最小值最大。求最大生成樹,然后在上面倍增詢問即可。

Life of the Party (ONTAK 2010)

Allowed Letters(CF 1009 G)

題目簡述:

你有6種字母,第i個字母有ci個。你要用這些字母排成一個字符串,其中有一些條件,第i個位置只能填某個字母的子集。問你能填出的字典序最小的字符串是什么。(sum ci<=10^5)

Solution

	首先求出最大匹配,下面考慮左邊點的情況。我們將匹配中的邊從右往左連,不在匹配中的邊從左往右連。這個時候一條增廣路成為一條連續的路徑。從每個左邊未匹配的點還是遍歷,如果被一個左邊的點被訪問到,說明存在一條增廣路,也就是不一定在最大匹配中。所有沒有被訪問到的點一定在最大匹配中。

Revmatching (TCO 2015 1A Hard)

題目簡述:
給定一個n個點的二分圖,每條邊有一個邊權。找到一個邊權和最小的邊集,使得刪掉這個邊集之后不存在完備匹配。n<=20
Solution

	根據Hall定理,只要存在一個集合S,使得|N(S)|<|S|,則不存在完備匹配。於是我們枚舉S集合,然后貪心刪除邊集使得|N(S)|<|S|。

Bajtman i Okrągły Robin (ONTAK 2015)

題目簡述:

有n個強盜,每個強盜會在時刻l到時刻r搶劫,會造成c的損失。在一個時刻,你可以選擇抓一個強盜,強盜被抓住之后不會造成損失。你要抓盡量多的強盜使得損失盡量小。(n<=5000)

Solution

	按強盜從大到小排序,貪心選取每個強盜能不能抓。判斷一些強盜能不能抓完,可以按左端點排序,使用優先隊列維護右端點。貪心算法的正確性: 考慮匈牙利算法,從大到小一個一個匹配,一個點一旦在匹配中,那么一直在匹配里面。

不知名例題

題目簡述:

平面上有n個點(x_i,y_i),將這些點紅藍染色使得每行每列紅藍點個數的差不超過1。生成2^n的01串(這個串頭尾相連),使得所有長度為n的01串都出現過。

不知名例題2

題目簡述:
有n個點,每個點有個權值ai,兩個點之間的邊權為(ai+aj) mod M。問最小生成樹。(N<=1e5,0<=M,a_i<=1e9)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM