圖論總結

Str_ywr發表於2024-07-04

重鏈剖分

樹上修改,查詢路徑資訊之類的 最多經過logn個輕邊, 這樣可以更好地劃分

注意點:

修改邊權可以轉化到點權上面: 注意lca的位置不要修改, 應該是update(id[y]+1,id[x])

例題:

輕重邊:

https://www.luogu.com.cn/problem/P7735

判斷是不是重邊,資訊轉化到點上面,邊兩端的顏色相同就是重(這樣好在圖上修改)


樹上維護顏色段數:

https://www.luogu.com.cn/problem/P2486

維護每個區間左右的顏色,以及每一個段的不同顏色個數,記錄左右端點的顏色


某一條路徑上,顏色為ci的有多少, 可以用動態開點每個顏色開一個線段樹:
https://www.luogu.com.cn/problem/P5838


P4211 [LNOI2014] LCA

經典套路: 求一個點和多個點的lca的深度和,可以轉化成:

比如z和l~r中每個點的lca的深度和:

l~r這些點到根的路徑全部+1,然後查詢z到根路徑上的點權和

對於多個區間的時候:

除了用主席樹:把每個右端點當做版本,
如果可以離線,並且滿足區間可減性: 可以把區間拆成左右端點,然後排序以後詢問


P3676 小清新資料結構題

這道題可以以1為根的時候的值作為基準, 推式子, 退出來以x為根的時候的答案會增大多少, 以及修改x權值的時候以1為根的值會增大多少

假設all是所有點權的和

對於第一個問題: 我們發現只有x->root路徑上的點的sz[u]會變假設從root->x的點是: a1,a2...an

會變成ai的貢獻是(all-sz[ai+1])^2(這裡的sz[u]是以1為根的)

所以會增大的就是dep[x]all^2 - all ^ 2 - 2all*(sz[a2]+sz[a3]+...+sz[adep])

對於第二個:就是sigema :(sz[u] + d) ^ 2 - sz[u]^2

發現只用知道x->root路徑上的sz[u]的和,這個直接樹剖維護即可


長鏈剖分:

  1. 長鏈剖分可以把維護子樹中 只與深度有關 的資訊最佳化到線性。
    用來解決樹上dp的維度裡面有長度這個維度的最佳化
    空間O(n), 時間O(n)!!!!!!!每個點在長鏈的頂端被合併一次

  2. nlogn預處理, O(1)查詢樹上k級祖先:

  3. 維護貪心

  4. 資料結構維護長鏈

注意

  1. f陣列在dp以後就會被覆蓋,也就是f[u][i]不一定表示u節點下面距離為i的了,可能已經變成1號節點為根的值了,所以如果有多組詢問要把詢問先存下來,用鄰接表的儲存關於u點的詢問,在dfs的時候就直接更新

  2. 如果有g[son[u]] = g[u] - 1; 那麼g的前後都需要h[v]的記憶體
    nw += h[v]; g[v] = nw; nw += h[v]

  3. 合併上來的時候,f[v][j] 的j下標必須<h[v]

  4. 記得內部要dfs下
    參考文獻:
    https://www.luogu.com.cn/blog/chen-zhe/zhang-lian-pou-fen-xiao-jie

例題:

P3565 [POI2014] HOT-Hotels

轉化成一個點下面掛兩個點,再再另一個子樹找一個點。如果有g[son[u]] = g[u] - 1; 那麼g的前後都需要h[v]的記憶體
nw += h[v]; g[v] = nw; nw += h[v]


k級祖先

跳2k以後的鏈一定>=2k

所以維護從top,向上向下跳x步到達的位置(x <= 鏈長)


更為厲害

長剖+懶惰標記

這道題需要得到所有到u距離<=i 的點的權值和(每個點的權值是sz), 由於長鏈是直接繼承上來的,所以繼承上來以後無法對於每一層都+=sz[u], 所以用tag的方式

繼承的時候把下面的tag加上來, 然後f[u][0] -= tag

程式碼裡的細節見註釋

注意:在tag更新的時候要更新f[u][0]


從根選擇k條到葉子的路徑,要求覆蓋點權最大:按照路徑長度剖分,選前k大


重建計劃

這道題用資料結構維護長鏈, 先分數規劃,然後轉化成L~R條邊裡面,路徑最長的是誰

線段樹的下標是id[u], id[u]+j表示距離u節點為j的所有點裡面,路徑最長為多少

然後合併,查詢即可(同一個長鏈上,由於id連續,所以可以直接繼承)

點分治

點分治基礎


例題:

P4178 Tree

P3806 【模板】點分治 1

acwing 264. 權值

樹上路徑=k, <=k. 直接點分治統計

然後在calc裡面: 樹狀陣列(map)/排序雙指標

點分樹

點分樹基礎

一共就兩條性質:

第一種是: 兩個點在點分樹上的lca, 一定在原樹中兩點的路徑上。 因為lca讓兩個點分開, 所以一定在路徑上。可以用這個來維護路徑:

例如震波, 開店

第二種就是: u點在點分樹上的子樹v, 在原圖中也是它的子樹(或者祖先上的那個子樹)

比如幻想鄉,捉迷藏


震波

這道題把兩點路徑變成了列舉兩個點的lca, 也就是不斷往上跳

每一個節點u, ans += u節點處<=k-dis的個數

為了避免重複, 還要 -= u的兒子v的子樹中到u<=k-dis的個數

用樹狀陣列和動態開點,都是nlogn的空間(後者*4)

動態開點,看似nlogn * logn 但是很多重複

這道題用樹狀陣列, 對於第一個樹狀陣列, 子樹到自己的距離最多是sz[u]

對於第二個, 雖然x和dfa[x]的距離在原樹中不一定是1, 但是由於dfa[x]的子樹最多sz[u]個節點, 所以開到sz[u]+1就可以了 (樹狀陣列需要整體右平移)

如果是動態開點, 開滿了,也就多一個4*...(因為一個節點真正有效的只有1~sz[u])


捉迷藏

這道題我們發現:

點分樹上一個點u的不同兒子v, 一定在原圖中u的不同子樹裡面(把u上方的那些點也當做一個子樹)

所以我們每個子樹傳上來一個最大值即可

我們可以用一個堆維護: q[u]表示u子樹的點到dfa[u]的距離集合
s[u]表示u的不同孩子送上來的最大值

ans表示答案序列


https://www.luogu.com.cn/problem/P3241

每個點維護一個年齡序列,按照年齡排序,維護一個距離字首和

然後每一次二分,加上字首和即可,因為lca(x,u)是dfa的時候,dfa集合內的點u到x的距離可以用u<->dfa + dfa<->x

注意:!!!getdis乘的數量,要排除掉當前子樹

兩個點在點分樹上的lca, 一定在原樹中,兩點的路徑上。 因為lca讓兩個點分開, 所以一定在路徑上


https://www.luogu.com.cn/problem/P3345

一種從根節點向下走的思路,在原圖上面:

如果從u->v 那麼sum[v] * 2 > sum[u]

那麼在點分樹上面, 對於u,假設原圖中滿足2sum>sum的點是x,點分樹上的兒子是v,那麼我們先走到v(因為x在v的子樹), 此時得到的代價是

nsum[u] - fsum[u] + edge[i].w * (sum[u] - sum[v])

nsum/fsum 分別表示到u和dfa[u], sigema si * di

同時我們還需要把x節點的d += sum[u] - sum[v]

因為之後從v移動的時候, 如果走向點分樹上的x,也一定在原圖中靠近x,因為上面的sum[u]-sum[v]是直接連結x的,所以這樣是對的


虛樹

詳細講解

O(klogk)把樹縮成只和關鍵點有關的大小

性質

  1. 虛樹的點數<=2k-1, 分叉點的個數<=k-1, 無分叉鏈(所有點都沒有分叉)的個數<=2k-1(考慮所有關鍵點及分叉點的父邊)

  2. 所有dfs相鄰兩點的lca等於任意兩點lca集合

  3. 虛樹的鏈並,等於相鄰兩點距離和的1/2:(最後一個和開頭也相鄰)

虛樹如果想要保留邊權資訊: 如果是和:可以直接維護一個dis

如果是max, 可以用倍增 或者 樹剖+st表

每一條邊,暴力向上跳

注意

  1. 建樹的時候是 while (id[lc] < id[stk[top-1]]) 是id[stk]!!!!!!!!!!
    是lc != stk[top-1] 或者id[lc] != id[stk[top-1]]

Kingdom and its Cities

首先排除相鄰的情況, 然後用f[u]表示u子樹滿足兩兩不連通的答案

有時候dp的狀態可以直接確定:比如這道題就可以直接確定子樹內是否有通向子樹外的關鍵點

g[u]表示上面的定義

如果u是關鍵點, 那麼g[u] = 1

如果u不是: 假設u子樹有兩個g[u]=1的,那麼這個時候一定要在u阻斷, 所以g[u] = 0, 如果有0個,g[u]也等於0

否則,如果只有一個g[u] = 1的, 那麼之後在阻斷一定不會更差,所以g[u] = 1,

假設子樹的g的和為sum

如果是關鍵點: f[u] = sigema f[v] + sum g[u] = 1;

如果不是: f[u] = sigema f[v] + (sum >= 2) g[u] = sum == 1


消耗戰
從1dfs到第一個im[u],這個點到1之間刪除一條邊即可


P4103 [HEOI2014] 大工程

任意兩點距離和: 統計沒一條邊的工薪

最大/小距離: 類似dp求直徑


尋寶遊戲

動態插入,刪除點, 求最小包含生成樹:

上面第3和結論, 用set維護id排序的序列,然後維護距離和


七彩樹

前置: https://www.luogu.com.cn/problem/CF893F

如果想要對S集合內的點到根(當做1)的路徑上的所有點(相同算一次),全部加上c

那麼首先對於S集合的點+=c,然後對於S集合按照dfs序排序以後:

相鄰的兩個點的lca-=c。 (頭和尾不算)

然後每個點的權值就是子樹和:

因為:對於一個點,假設內部有k個關鍵點,那麼在內部的lca數是k-1(相鄰的點求lca),所以子樹和正好+=c

如果想要把S集合的完整虛樹上的點+=c,完整虛樹的root = lca(a1, an)

那就相當於上面的去掉了root<->1的這些點

所以就在上面的基礎上,對於fa[lca(a1, an)]-=c

回到這道題:

假設不考慮深度: 一個顏色會被包含在它所有祖先節點的子樹裡,所以對於一種顏色集合S,我們需要把這些點到根的路徑上的所有點(相同算一次),全部加上1,代表有貢獻。 利用上面的差分,一個點子樹的和,答案

因為有深度限制,那麼就用前置裡的可持久化線段樹的思路,按照深度加點

然後每個顏色用一個set,按照id排序, 維護lca即可


世界樹

https://www.luogu.com.cn/blog/on-the-way/xu-shu-p3233-hnoi2014-shi-jie-shu


考慮異或k本質上是什麼: 首先對於n個數建立trie, 那麼如果第k層是1, 就相當於把所有k層的左右孩子交換一下, 對於一條從1~葉子鏈x, 原本的排名是a, 現在要求變成b, 那麼需要+m(m = b - a)

x鏈的第i層如果是1, 那麼交換以後會-=sz[i][0], 如果是0,交換以後會+=sz[i][1]

這個操作只會在有分叉點有用

那麼這個就相當於一個可行性揹包: 用ai湊m

這個可以用bitset最佳化, bitset f[i]表示用前i個,能不能湊出來bitset對於的若干個m

轉移f[i] = f[i-1], f[i] |= f[i-1] << ai; 或者 f[i] |= f[i-1] >> -ai(ai<0)

我們發現對於每一個n都求一遍好像比每一次詢問都求要好, 但是發現存不下

那麼就離線下來, 然後dfs這個trie

到達葉子就更新答案

這個圖可以看成是n個葉子點和1為關鍵點的完整虛樹,總點數依然是nlogn。但是有2個兒子的節點<=n-1個(因為每一個這樣的節點合併兩個關鍵點)。所以無分叉鏈的個數<=2n-1(葉子和分叉的節點)。所以只有2n-1個物品

總複雜度n^2/w 每一次轉移O(m)


https://www.luogu.com.cn/problem/P4242

樹剖維護顏色, 虛樹上dp, f[u]表示不考慮自己到自己, 到子樹內點的距離和


https://www.luogu.com.cn/problem/P6071

性質: 一個點x加入虛樹,他的父節點就是dfs序<id[x]的最大節點和它的lca, 以及dfs序>id[x]的最小節點和x的lca裡面深度更大的那個

一個虛樹的根,就是idmin和idmax的lca


https://www.luogu.com.cn/problem/P5439

我們發現直接計算每一個路徑上的貢獻不太好做, 那麼轉換求和順序, 找每一對點被幾個路徑包含

那麼就會產生兩種思路:

點分治, 或者分類討論是否一個點是另一個點的祖先

方法1: 虛樹
https://www.luogu.com.cn/blog/command-block/ds-ji-lu-p5439-xr-2-yong-heng

建立線性虛樹: 可以再點分治的時候把每個點屬於的虛樹編號都存下來,結束以後,從dfs序大的點到小的點遍歷,加入到這個點所屬的所有虛樹裡面,這樣每個虛樹的點都是從小往大排序的了(因為鏈式前向星是反著的)

方法2: 樹剖
https://www.luogu.com.cn/blog/Owencodeisking/solution-p5439

對於lca(x,y) != x 且!=y, 這種情況就是sz[x] * sz[y] * dep(lca)

deplca這類的問題, 可以轉化成在T2上面x到根的路徑上的點都+1, 然後查詢y到根的路徑的和就行了

但是我們發現還有szx * szy 所以T2到根的路徑都+szx, 然後最後的和再*szy就行了

對於第二種情況多算的,再去減掉就行


https://www.luogu.com.cn/problem/P5360

預處理字首和字尾最小生成樹

並且以左邊一列和右邊一列為關鍵點,建立虛樹,每條邊只保留最大的那條作為這條邊的邊權。然後儲存一下如果把每一條邊的最大邊權都刪掉,所得到的最小生成樹的大小sum

以x列分裂的時候,就是1~x-1 和 x+1~m的生成樹按照第1列和最後一列的最小生成樹,中間的邊的邊權是最大的那條邊,選擇了就代表不刪除,不選擇就代表刪除,不選擇就代表不刪除,不聯通。

中間建立虛樹的時候,可以用dfs的方式,只有son>=2或者是關鍵點再建立


https://www.luogu.com.cn/problem/CF526G

https://www.luogu.com.cn/blog/command-block/ds-ji-lu-cf526g-spiders-evil-plan

k條路徑等價於葉子<=2k的樹,所以就是一個x為root,選擇<=2k個節點的最長路徑(長鏈剖分)

如果多組詢問, 發現一定會有一個直徑的端點被選擇,所以以直徑的端點作為根,然後調整覆蓋x即可(注意,兩個直徑端點都可能),直徑的端點一定是葉子,所以選擇2y-1條

調整的時候,把x子樹裡面最深點t到根的路徑加入。我們發現第2y-1條長鏈,要麼出現在t的祖先中最深的地方(否則就不是最短的),要麼就不和t到祖先的路徑重合

第一種情況找到後刪掉後半部分,第二種,直接刪掉2y-1這條

Boruvka

  1. 核心: 每個點選擇一個最小的邊,然後擴充套件

每一輪點數至少/2, 所以總共執行logn輪

  1. 應用範圍: 用於一類完全圖,邊數特別多的時候,轉化成找到每個點的最小邊

  2. 注意事項:

每一次的getto要提前, 每一次要初始化g, 還有flag, bet函式在邊權相同的時候不用按照第二關鍵字排序(一般寫法需要)

例題:

https://www.luogu.com.cn/problem/CF888G

第一種是用boruvka, 然後每個集合開一個trie, 合併的時候啟發式合併, 每一次對於每個點在除去自己的trie裡面找最小

第二種是發現一共有n-1個2個節點的點, 這個節點的兩側各選擇一個葉子,形成一條邊,這個過程就是列舉左兒子的所有節點,然後在右兒子的子樹找,找到最小的


https://www.luogu.com.cn/problem/AT_cf17_final_j

拆分成wx+disx+wy+disy-dislca, 對於每個x,求min wy+disy-dislca

為了防止同一個,就不同種類的最小值都記錄一下,還要記錄具體是哪個種類

void update(int u, ll x, int id) {
	if (x < f[u][0]) {
		if (id != g[u][0]) {
			f[u][1] = f[u][0]; g[u][1] = g[u][0];
		}
		f[u][0] = x; g[u][0] = id;
	} else if (x < f[u][1]) {
		if (id != g[u][0]) {
			f[u][1] = x; g[u][1] = id;
		}
	}
}

//!!!!!!!!!!如果邊權有<0的, 那麼換根的時候有可能是錯的, 因為假設fa[v]=u
//f[u]的最小節點就在v裡面, 那麼重複走一條負數可能會導致結果變小
//如果沒有顏色的限制, 我們同樣可以利用0/1維護不同子樹的min以及具體來自哪2個子樹, 然後更新
//但是如果有, 我們對於u,可以先正著列舉兒子 然後反著列舉, 維護一個字首最小, 然後更新子樹dfs ,先進入兒子,再用兒子更新字首最小。


https://www.luogu.com.cn/problem/P6199

兩個dis不好計算,把其中一個樹進行點分治, 然後發現可以把dis轉換成wi+wj

這個w是到當前分治中心的距離

然後建立虛樹,像tree mst一樣求一下就行了

這裡建立虛樹可以不用帶log:
建立線性虛樹: 可以再點分治的時候把每個點屬於的虛樹編號都存下來,結束以後,從dfs序小的點到大的點遍歷,加入到這個點所屬的所有虛樹裡面,這樣每個虛樹的點都是從小往大排序的了(用vector儲存)



最小斯坦納樹

基本思想:

fi, s表示以i為根,包含了s這些關鍵點的最小代價

轉移: 分類討論i的度數, 如果不是1,那就考慮fi,sn + fi,s^sn。如果是1, 那麼就考慮是由j,s轉移。

複雜度: 第一個轉移是n*3^n

第二個是mlogm*2^n

注意,先轉移第一個,也就是同層的,然後轉移不同層的

例題:

https://www.luogu.com.cn/problem/P4294

按照點權dp, 記錄一下過來的方向

void dfs(int i, int s) {
	if (g[s][i] == -1) return;
	if (g[s][i] > 0) {
		dfs(i, g[s][i]); dfs(i, s ^ g[s][i]);
	}
	else {
		us[i] = 1; dfs(-g[s][i], s);
	}
}
for (int i = 1; i <= k; i++) us[key[i]] = 1;

us不一定把所有關鍵點都標記了,但是非關鍵點並且在樹中的都標記了


圓方樹

https://www.luogu.com.cn/blog/on-the-way/yuan-fang-shu

例題:

Prufer

https://www.luogu.com.cn/blog/on-the-way/prufer

支配樹

https://www.luogu.com.cn/blog/on-the-way/zhi-pei-shu

最大團

https://www.luogu.com.cn/blog/on-the-way/bronkerbosch

環的計數

https://oiwiki.org/graph/rings-count/

例題1: 如連結的t1


例題2: 三元環計數

讓度數小的指向度數大的(大連結小也行), 然後遍歷出邊,再遍歷出邊的出邊是否能到達第一步的位置

注意:度數相同的時候要按照編號來比較大小

複雜度: msqrt(m) 證明: 相當於對於每一條邊u->v, sigema out[v]

對於出度<sqrt(m)的點(一共m個邊),msqrtm

對於出度>sqrt(m)的點,它連出去的點的度數一定>sqrt(m), 所以最多sqrt(m)個,總共msqrt(m)

如果由重邊,合到一起作為邊的邊權,然後每一個三元環提取出來三條邊的權值的乘積 用ed[i]表示來到i的那條邊


例題3: 四元環計數

具體思路:

每次找到最大點a, 然後找到由多少個c滿足, (a,b) (b,c)有邊(b,c都要小於a, 但是c不用小於b)

然後對於一個固定的c, ci有toti個, 那麼就是sigema C(toti,2)

然後清空tot陣列

注意:一定是度數大的連線到度數小的

const int N = 1e5 + 10;
vector<int> e[N], g[N]; ll ans;
int n, m, deg[N]; int tot[N];
//!!!!!!!!!四元環必須是從大度數到小度數
//因為四元環: 先列舉有向邊, 然後列舉的是原圖的無向
//複雜度是: sigema in[u](對於每一個原圖的無向)
//只有從大度數連到小度數, 才能保證in<=sqrt(n)   如果是從小->大, 那麼是out<=sqrt(n)
//三元環計數, 按照in和out都可以算複雜度, 但是這個只能按照in算複雜度 
int main() {
	n = read(), m = read();
	for (int i = 1; i <= m; i++) {
		int u = read(), v = read();
		e[u].push_back(v); e[v].push_back(u);
		deg[u]++; deg[v]++;
	}
	for (int u = 1; u <= n; u++) {
		for (auto v : e[u]) {
			if (deg[v] < deg[u] || (deg[v] == deg[u] && v < u)) g[u].push_back(v);
			//因為一開始建立的就是雙向的, 所以這樣一定有一條是對的 
		}
	}
	for (int a = 1; a <= n; a++) { //a是最大點 
		for (auto b : g[a]) { //找到前面的b,然後找c。b,c都要<a
			for (auto c : e[b]) { //第二個是原圖, 因為不要求c < b 只要求a是最大 
				if (deg[c] < deg[a] || (deg[c] == deg[a] && c < a)) { //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!c不能大於a 
				//如果c = a也不能算 
				//因為a,b,c,d (a<b<c<d)和a,c,b,d都是合法的方案,所以第二次要用原圖 
					ans += tot[c]; tot[c]++;
				}
			}
		}	
		for (auto b : g[a]) {
			for (auto c : e[b]) { //!!!!!!!!!!!!!!!!!!!!!!!!!!!記得清空 
				tot[c] = 0;
			}
		}	
	}
	cout << ans;
	return 0;
}

完美消除序列

https://www.cnblogs.com/onglublog/p/14507852.html

https://www.cnblogs.com/zhoushuyu/p/8716935.html

  1. 弦圖判定:

先得到一個完美消除序列:lable的倒序(lable是當前節點和幾個已經加入節點有邊相連線)

然後判斷每個a[i]和N(a[i])是否構成團, N(a[i])表示和a[i]有連邊,在a[i]後面的點

複雜度O(n+m)


const int N = 1e3 + 10;
vector<int> e[N], to[N];//to表示所有lablei = x的有那些
int lab[N], rnk[N], a[N], n, m; //  num表示lablei 
void msc() {
	for (int i = 1; i <= n; i++) to[0].push_back(i); 
	int mx = 0;//表示當前最大的lable 
	for (int p = n; p >= 1; p--) {
		bool flag = 0; int x = 0;
		while (!flag) {
			for (int j = to[mx].size() - 1; j >= 0; j--) {
				int v = to[mx][j];
				if (!rnk[v]) {
					x = v; flag = 1; break;
				} else to[mx].pop_back();
			}
			if (!flag) mx--;
		} rnk[x] = p; a[p] = x;
		for (auto v : e[x]) {
			if (!rnk[v]) {
				to[++lab[v]].push_back(v); mx = max(mx, lab[v]);
			}
		}
	}
}
bool vis[N]; int tmp[N];
bool check() {
	//每一次找到有邊的最近的那個 
	for (int i = 1; i <= n; i++) {
		int sz = 0; int u = a[i];
		for (auto v : e[u]) {
			if (rnk[v] > rnk[u]) {
				tmp[++sz] = v, vis[v] = 0; //所有有邊的點 
				if (rnk[tmp[sz]] < rnk[tmp[1]]) swap(tmp[1], tmp[sz]);//tmp[1]是最靠前的 
			}
		}
		for (auto v : e[tmp[1]]) vis[v] = 1;
		for (int j = 2; j <= sz; j++) {
			if (!vis[tmp[j]]) return 0; //tmp[1]不能算 
		}
	}
	return 1;
}
void init() {
	for (int i = 1; i <= n; i++) {
		e[i].clear(); lab[i] = 0; rnk[i] = 0; to[i].clear();
	}
}

  1. 弦圖的極大團

x + N(x)一定是一個合法的, 我們預處理first[u], 表示第一個和u有連邊的點

那麼如果sz[u] >= sz[fst[u]] + 1, 說明fst被完全包含,就不要了,vis[fst[u]]=1. 那些vis[u]=0的點是合法的

最大團就是所有sz的max

  1. 弦圖的色數

色數=最大團

//	for (int i = n; i >= 1; i--) { //方案 
//		int u = a[i];
//		for (auto v : e[u]) if (col[v]) vis[col[v]] = 1;
//		int c = 1; while (vis[c]) c++;//一定<=deg 
//		col[u] = c;
//		for (auto v : e[u]) if (col[v]) vis[col[v]] = 0;
//	} 
	int ans = 0;
	for (int i = 1; i <= n; i++) {
		int u = a[i]; int sz = 1;
		for (auto v : e[u]) {
			if (rnk[v] > rnk[u]) sz++;
		} ans = max(ans, sz); //等於最大團大小 
	}
  1. 弦圖的最小團覆蓋/獨立集大小

顯然一個團內只能取一個點,而獨立集大小即為最小團覆蓋。
對於序列中的每個點,把之後的點全部打上標記即可。

int ans = 0;
for(int q = 1; q <= n; q++) {
    int x = id[q];
    if(!vis[x]) {
        ans++;
        for(int i = head[x]; i; i = nxt[i])
            vis[ver[i]] = 1;
    }
}

全域性最小割

https://www.luogu.com.cn/blog/on-the-way/stoer-wagner

圖的絕對中心

我們發現這些交點處是最優的地方(和u,v軸相交的就是中心在點上的情況)

交點的特徵就是我們從距離u從大往小:i1->i2->i3

如果發現某一個點: dis[v][i3]>max(dis[v][i1,...i2]),那麼就會出現交點

這個時候的長度就是dis[u][i3] + dis[v][maxindx:i1] + w, 因為紅色交點表示的是i1更靠近v(下降階段), i3更靠近u(上升階段)

最小直徑生成樹就是以絕對中心為根的最短路徑樹(記錄一下每個點的最短路徑是從哪裡來的)

帶花樹

https://www.luogu.com.cn/blog/on-the-way/dai-hua-shu

Hall定理

https://www.cnblogs.com/came11ia/p/16676776.html

對於二分圖中vl <= vr:存在vl的匹配當且僅當 vl中的任意一個集合S, 和S集合每個點出邊構成的集合Q,S <= Q

擴充: vl<=vr的圖裡面最大匹配等於 vl - max(0,S-Q)

bzoj3693: 圓桌會議

這道題轉換成左邊人和右邊的座位的完全匹配

!!!!!!!!!!!!!!!一定要判斷vl<=vr, 如果vl>vr一定不成立

左邊的人拆成ai個,就變成了,左邊任意選擇,右邊必須大於左邊

但是發現一組人一下子選完肯定限制更嚴格。

所以轉化成若干個區間,每個區間有權值,看能不能選擇若干個區間,ai的和>長度

把區間排序,假設選擇了當前區間,線段樹維護一下最左邊區間是i的時候的值。找個最大值,比較一下是否大於0即可

這道題是環形的,所以還要2倍

上下界網路流


可行流,先把底線流滿,算出每個點的in,out

如果in>out, 那麼從s->i, 邊權是in-out, sum += in-out

如果in<out 那麼從i->t, 邊權是out-in

判斷最後dinic是否等於sum,等於就合法

每條邊的流量是網路的流量+下界


上下界最大流

按照可行流建圖,然後連ed->st inf

從s->t跑可行流,st->ed的反相邊就是可行流,然後再從st->ed跑最大流(把ed->st的邊刪掉,edge[cnt].w = edge[cnt^1].w = 0)

結果相加是答案


最小流,按照最大流跑, 只不過需要從ed->st跑最大流,結果是第一次-第二次


最小費用可行流(或者最大流也行???):

按照可行流建邊,然後跑費用流

線性規劃轉網路流

https://www.cnblogs.com/rainybunny/p/15700263.html

  1. 對於這個連結裡面的套路適用於:

min: sigema buxi + sigema I * max(0, xu-xv-c)

如果bu>0 那麼建立s->i的邊,如果<0,那麼建立i->t的

後面的連結v->u 上線為I, 費用為c的邊

最後跑最小費用, 相反數就是答案

  1. 對偶問題的轉化:

大於小於號變, 約束條件和結果邊, 變數變

比如:

min sigema xici

lim:

對於每個j,找到所有si<=j<=ti的xi求和: sigema xi >=aj

變成

max sigema yjaj

lim:

對於每個i, 找到所有si<=j<=ti的yj求和: sigema yj<=ci

ci和aj互換, <=和>=換, min和max換

xi和yj換(下標也換), 列舉的範圍也換(i->j)

平面圖

點-邊+面=2

https://www.luogu.com.cn/blog/on-the-way/ping-mian-tu

網格圖最小割轉最短路:

從左上角到右下角的最小割,從左到右和從上到下的邊有用,所以所有邊順時針轉90, 構造對偶圖,從右上角到左下角的最短路

同餘最短路

用來解決用若干個a,b,c,d,e....(假設a<=b<=c<=d...)

能不能湊出來某個數,或者能湊出來多少個<=k的

具體方法,任意一個數,我們把它放在最小模數a的剩餘系裡面

那麼我們求一下用剩下的b,c,d...能湊出來%a=i的最小值是多少,記為h[i]

那麼建圖:

每個i到(i+b)%a 邊權b, i到(i+c)%a 邊權c

跑最短路,就是h[i]

判斷能不能湊出n, 就是看一下h[n%a]是不是<=n

如果是,那麼一定可以不斷+a,湊到n

否則一定不行

判斷能湊出來多少個<=的數

那麼對於每一i, ans += (n-h[i])/a + 1

h[i]每當+a,就可以湊出來一個新的

對於2個的時候:

ax+by=n的解

相當於問by=n(mod a)

y = invb * n 對a取mod, 然後判斷一下y*b是否>n,如果不大於,那麼就合法

求=n的方案數,就是想跳樓機那樣求一下前n-1的字首和,然後求一下前n的字首和。減一下就行了

例題:

https://blog.csdn.net/weixin_45750972/article/details/118581945

https://www.luogu.com.cn/problem/CF986F

相關文章