浙江金華 圖論整理

圖論

基礎知識儲備:

(1)、概念:

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

(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都成立。
公式spa

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

(4)、DFS Forest簡介:

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

(5)、歐拉回路

①簡介:

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

②代碼實現:

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)rest

④最小瓶頸生成樹簡介:

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

⑤生成樹計數簡介:

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

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,那麼添加一些邊將這些連通塊連通的生成樹個數爲a1a2...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)

相關文章
相關標籤/搜索