重鏈剖分
樹上修改,查詢路徑資訊之類的 最多經過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]的和,這個直接樹剖維護即可
長鏈剖分:
-
長鏈剖分可以把維護子樹中 只與深度有關 的資訊最佳化到線性。
用來解決樹上dp的維度裡面有長度這個維度的最佳化
空間O(n), 時間O(n)!!!!!!!每個點在長鏈的頂端被合併一次 -
nlogn預處理, O(1)查詢樹上k級祖先:
-
維護貪心
-
資料結構維護長鏈
注意
-
f陣列在dp以後就會被覆蓋,也就是f[u][i]不一定表示u節點下面距離為i的了,可能已經變成1號節點為根的值了,所以如果有多組詢問要把詢問先存下來,用鄰接表的儲存關於u點的詢問,在dfs的時候就直接更新
-
如果有g[son[u]] = g[u] - 1; 那麼g的前後都需要h[v]的記憶體
nw += h[v]; g[v] = nw; nw += h[v] -
合併上來的時候,f[v][j] 的j下標必須<h[v]
-
記得內部要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)把樹縮成只和關鍵點有關的大小
性質
-
虛樹的點數<=2k-1, 分叉點的個數<=k-1, 無分叉鏈(所有點都沒有分叉)的個數<=2k-1(考慮所有關鍵點及分叉點的父邊)
-
所有dfs相鄰兩點的lca等於任意兩點lca集合
-
虛樹的鏈並,等於相鄰兩點距離和的1/2:(最後一個和開頭也相鄰)
虛樹如果想要保留邊權資訊: 如果是和:可以直接維護一個dis
如果是max, 可以用倍增 或者 樹剖+st表
每一條邊,暴力向上跳
注意
- 建樹的時候是 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
- 核心: 每個點選擇一個最小的邊,然後擴充套件
每一輪點數至少/2, 所以總共執行logn輪
-
應用範圍: 用於一類完全圖,邊數特別多的時候,轉化成找到每個點的最小邊
-
注意事項:
每一次的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
- 弦圖判定:
先得到一個完美消除序列: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();
}
}
- 弦圖的極大團
x + N(x)一定是一個合法的, 我們預處理first[u], 表示第一個和u有連邊的點
那麼如果sz[u] >= sz[fst[u]] + 1, 說明fst被完全包含,就不要了,vis[fst[u]]=1. 那些vis[u]=0的點是合法的
最大團就是所有sz的max
- 弦圖的色數
色數=最大團
// 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); //等於最大團大小
}
- 弦圖的最小團覆蓋/獨立集大小
顯然一個團內只能取一個點,而獨立集大小即為最小團覆蓋。
對於序列中的每個點,把之後的點全部打上標記即可。
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
- 對於這個連結裡面的套路適用於:
min: sigema buxi + sigema I * max(0, xu-xv-c)
如果bu>0 那麼建立s->i的邊,如果<0,那麼建立i->t的
後面的連結v->u 上線為I, 費用為c的邊
最後跑最小費用, 相反數就是答案
- 對偶問題的轉化:
大於小於號變, 約束條件和結果邊, 變數變
比如:
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