1. 程式人生 > >Noip前的大抱佛腳----圖論

Noip前的大抱佛腳----圖論

圖論

知識點

Tarjan相關

邊雙和點雙/割邊和割點

強連通分量:有向圖中任意兩個頂點都有相互到達的路徑的一個極大子圖
邊雙連通分量:一個子圖中刪去任意一條邊都不影響圖的連通性
點雙連通分量:一個子圖中刪去任意一個點都不影響圖的連通性
割邊:連線兩個邊雙的邊
割點:連線兩個點雙的點

程式碼如下:

//把一個邊雙縮點
void Tarjan(int x)
{
    vis[x]=1;sta[++top]=x;
    dfn[x]=low[x]=++tot;
    for(int i=head[x];i;i=a[i].next)
    {
        int R=a[i].to;
        if(!dfn[R]) Tarjan(R),low[x]=min(low[x],low[R]);
        else if(vis[R]) low[x]=min(low[x],low[R]);
    }
    if(low[x]!=dfn[x]) return;
    for(int k=sta[top],lst=0;lst!=x;lst=k,k=sta[--top])
        vis[k]=0,bel[k]=x;
}
//無向圖縮點略有不同
//求割點(tag[x]=1)
void Tarjan(int x,int f)
{
    int s=0;dfn[x]=low[x]=++tot;
    for(int i=head[x];i;i=a[i].next)
    {
        int R=a[i].to;if(R==f) continue;
        if(!dfn[R])
        {
            s++;Tarjan(R,x);tag[x]|=low[R]>=dfn[x];
            low[x]=min(low[x],low[R]);
        }
        else low[x]=min(low[x],dfn[R]);//注意!這裡必須是dfn,有反例!!
    }
    if(!f&&s==1) tag[x]=0;
}

有向圖縮點後成為DAG,無向圖縮點/求割點後成為樹的結構

TarjanLCA

\(O(n*反阿克曼函式+Q)\)離線求lca

步驟:

  • 對詢問掛鏈(vector),初始化並查集
  • \(dfs\)整棵樹後處理詢問,掃完該子樹才將其並查集父親指向樹結構的父親
  • 如果對於一個詢問\((x,y)\),當前掃到\(y\)\(x\)已經被訪問過,答案為x的並查集父親
void tarjan(int x,int fr)
{    
    for(int i=head[x];i;i=a[i].next)
        if(R!=fr) tarjan(R,x);vis[x]=1;
    for(int i:U[x])
    {
        int y=g[i]^x,&s=ans[i];//g[i]=x^y,表示第i個詢問
        if(vis[y]) s=find(y);
    }
    fa[x]=fa[fr];
}

圓方樹

二分圖相關

DFS找奇環

From [CodeForces19E] Fairy

如果一條邊只存在於奇環中,那麼DFS時一定能夠判斷出這條邊只會存在於奇環中

why?

並查集維護二分圖

在資料結構有講,用按秩合併的帶權並查集維護黑白顏色即可

二分圖匹配的不可行邊

求一定不會出現在二分圖最大匹配中的邊

方法是:跑網路流,對殘餘網路跑\(Tarjan\)縮點,一條邊不是不可行邊當且僅當它是匹配邊或者他們被縮在同一個點中

感性理解:縮在同一點,把環給反向,於是就可以變成匹配邊了

注意:Dinic跑二分圖複雜度為\(O(\sqrt n m)\)!!

最小生成樹相關

最短路樹

要求生成樹中每個點到1號點的距離等於圖中最短路,且生成樹邊權之和最小

這個直接用SPFA或者Dijkstra求從哪裡來是最短路,然後貪心選個最短的就好了

最短路相關

負環

可以使用SPFA,先同時把所有點入隊,然後判斷一個點的最短路如果經過超過\(n\)個點,則表示存在負環

多源最短路

把源點同時入隊/入堆,然後照樣跑即可,同時如果需要可以記一個from表示從哪個源點過來的

如果不需要記錄,新建源點向所有源點連0的邊即可

差分約束系統

連邊以及跑最長/短路

對於一些形如 \(v_a\le v_b+W\) 的若干關於"\(\le\)“的式子,可以從從\(b\)\(a\)連一條權值為\(W\)的邊,然後跑最短路
最後跑出來的答案\(dis[a]\)表示的是關於\(v_a\)的若干不等式的解,就是說不等式組中最小的那個,所以\(dis[a]\)\(v_a\)的最大值

如果是要求最小值,那麼對於上面的式子,可以從\(a\)\(b\)\(-W\)的邊,跑最長路,得出的\(dis[b]\)\(b\)的最小值

無解以及做題方法

可以通過存在正/負環判斷無解

很多時候模型並不明確,可以二分答案之後判斷合法性來求解一般問題

01最短路

邊權只有0或1的最短路,感覺是Dijkstra+SPFA,操作是開一個雙端佇列deque,每次更新最短路,如果由+0更新就丟隊頭,+1更新就丟隊尾,然後每個點只往外面更新一次,其實是用佇列模擬Dij的堆,這樣複雜度是\(O(m)\)

k短路

  • 求一張有向圖中\(1\)\(n\)中第\(k\)短的路徑,可以不是簡單路徑(有環,自環,重邊)

    做法1:A*搜尋

    建出以\(n\)為根的最短路樹,那麼每個點有兩個引數:\(f[i]\)表示到達\(i\)點已經走了\(f[i]\)的長度,\(h[i]\)表示\(i\)的估價函式,表示\(i\)\(n\)最少還要走多遠,以這個為權值每次在堆中選取權值最小的更新,\(n\)被走到的第\(k\)次即為\(k\)短路

    做法2:可持久化左偏樹

    建出最短路樹後我們發現對於任意一條路徑,可以拆成樹邊和非樹邊,且一個非樹邊的邊集對應著一條路徑,於是我們需要求出第\(k\)小的非樹邊集

    \(dis[i]\)表示\(i\)\(n\)的最短路長度,則定義一個非樹邊集\(S\)的權值為\(dis[1]+\sum_{e\in S}d[e],d[e]=dis[to]+e.w-dis[fr]\)\(d[e]\)的實際含義是走這條邊新增的代價,於是把這個丟進優先佇列裡取出\(k\)次就好了

    然後我們考慮怎麼得到非樹邊集&怎麼拓展新狀態。

    對於每個點,維護該點到樹根(n)的路徑的所有非樹邊的出邊集,並按照出邊的\(d[e]\)排序(這個用可持久化左偏樹得到,每次從父親那裡copy下來,程式碼見魔法豬學院)

    定義一個全域性的優先佇列按照\(bs+val\)排序,\(bs\)表示已經走了的邊的權值,\(val\)表示當前邊集的權值。首先把1號點的左偏樹樹頂丟入全域性堆中,然後從堆中取出k次元素得到第k小

    對於一個狀態是當前最小,由此可以拓展出三個新狀態:該點的左偏樹中的左右兒子(表示為從該點向上不選擇原來的非樹邊,轉而選擇一條比它大一點的非樹邊。需要堆中父親一定大於左右兒子的性質做保證)、該條邊指向的點的堆頂(表示為一定選這條邊,在往n走的路徑中再選一條非樹邊,此時要給\(bs_{new}+=bs_{old}+val_{old}\)

    複雜度:\(O(nlogn+mlogm+klogk)\),分別為最短路、左偏樹、優先佇列的複雜度之和

網路流

zkw費用流

用SPFA跑出最短路之後,用類似Dinic的dfs跑很多遍,對於相同費用的路徑較多的圖的效率特別高,當然不滿足這種性質效率就會比一般的費用流效率低(無限之環加上zkw費用流快了100倍!)

int dfs(int x,int flow)
{
    if(x==T) return flow;vis[x]=1;
    for(int &i=cur[x];i;i=a[i].next)
    {
        int R=a[i].to;//dis表示的費用最短路
        if(!a[i].w||dis[R]!=dis[x]+a[i].cost||vis[R]) continue;
        int k=dfs(R,min(flow,a[i].w));
        if(k) {a[i].w-=k,a[i^1].w+=k;vis[x]=0;return k;}
    }
    return 0;//如果在這個點找不到增廣路,vis就只會在SPFA中清空,這個點在本次dis陣列時不會再訪問
}

當然是建圖技巧啦

  • 網格w

做題經驗

同餘類最短路

來源:墨墨的等式
\(a_1x_1+a_2x_2+...+a_nx_n=B\),其中\(x\)都有非負整數解,求\(B\)在一定範圍內的取值個數

巧妙地把每個解歸類為\(\%a_1\)的餘數那個點,對每個點往外連邊跑最短路,表示得到\(\%a_1=k\)的最小的\(B\)是多大,從而推出\(B\)的個數

邊權是max的形式

代價是進入該點和從該點出去的邊權\(max\),求\(1\)\(n\)的最小代價

注意題目是無向圖,所以可以每條邊拆成兩個,然後左右對應,差分建圖

其實遇到max都應該要想想差分

圖論模型的轉換

遇到以下情況可以考慮圖論模型來連邊

  • 有兩類點,兩類點之間有關聯,然後可以考慮把A類點像B類點的兩條邊轉為B類點的一條邊(ZKJ太陽月亮匹配/棋盤上的守衛)
  • 兩個點相互關聯,知道其中一個就可以推導另一個
  • 一個物品有兩個權值,權值值域一定(ZKJ煙火左右權可翻轉,求最大權連續序列)

邊定向

這是一種思考方向,可以參考[BZOJ4883]棋盤上的守衛

樹上點覆蓋

一個關鍵點能夠覆蓋距離它不超過k的點,要求覆蓋所有點的最小關鍵點數

這題是貪心不是DP啊QAQ

兩樹疊圖的最小割及方案數

答案為2或者3,所以一定是A樹斷一邊,B樹斷若干邊構成的

把A作為樹形結構,B樹的每條邊\(tag[x]++,tag[y]++,tag[lca(x,y)]-=2\)

所以子樹之和便是子樹內向外面連的邊數之和

一類BFS最小生成樹做法

給定網格圖/樹結構,求一些關鍵點的最小生成樹(10.15YLT3)

方式是以關鍵點為源跑多源最短路,一個點被經過的第二次/一條邊連線的兩點的最近源點不同,就可以連線這兩個源點了。注意邊數還是原來的邊數級別的