系列索引:html
https://oi-wiki.org/graph/heavy-light-decomposition/算法
qRange
:將樹從 $x$ 到 $y$ 結點最短路徑上全部節點的值都加上 $val$updRange
:求樹從 $x$ 到 $y$ 結點最短路徑上全部節點的值之和qSon
:將以 $x$ 爲根節點的子樹內全部節點值都加上 $val$updSon
:求以 $x$ 爲根節點的子樹內全部節點值之和時間複雜度 $O(n\log^2n)$。ui
int w[N], wt[N]; int t[N<<2], laz[N<<2]; int son[N], id[N], fa[N], dep[N], siz[N], top[N]; inline void pushdown(int k, int len) { laz[k<<1]+=laz[k]; laz[k<<1|1]+=laz[k]; t[k<<1] = (t[k<<1] + laz[k]*(len-(len>>1))) % MOD; t[k<<1|1] = (t[k<<1|1] + laz[k]*(len>>1)) % MOD; laz[k]=0; } void build(int k, int l, int r) { if (l==r) {t[k] = wt[l] % MOD; return; } int mid=(l+r)>>1; build(k<<1, l, mid); build(k<<1|1, mid+1, r); t[k] = (t[k<<1] + t[k<<1|1]) % MOD; } int query(int k, int l, int r, int x, int y) { if (x<=l && r<=y) {return t[k]; } if (laz[k]) pushdown(k, r-l+1); int mid=(l+r)>>1, res=0; if (x<=mid) res = (res + query(k<<1, l, mid, x, y)) % MOD; if (y>mid) res = (res + query(k<<1|1, mid+1, r, x, y)) % MOD; return res; } void update(int k, int l, int r, int x, int y, int val) { if (x<=l && r<=y) {laz[k]+=val, t[k]+=val*(r-l+1); return; } if (laz[k]) pushdown(k, r-l+1); int mid=(l+r)>>1; if (x<=mid) update(k<<1, l, mid, x, y, val); if (y>mid) update(k<<1|1, mid+1, r, x, y, val); t[k] = (t[k<<1] + t[k<<1|1]) % MOD; } inline int qRange(int x, int y) { int ans=0; while (top[x]!=top[y]) { if (dep[top[x]] < dep[top[y]]) swap(x, y); ans = (ans + query(1, 1, n, id[top[x]], id[x])) % MOD; x=fa[top[x]]; } if (dep[x]>dep[y]) swap(x, y); ans = (ans + query(1, 1, n, id[x], id[y])) % MOD; return ans; } inline void updRange(int x, int y, int val) { val %= MOD; while (top[x]!=top[y]) { if (dep[top[x]] < dep[top[y]]) swap(x, y); update(1, 1, n, id[top[x]], id[x], val); x=fa[top[x]]; } if (dep[x]>dep[y]) swap(x, y); update(1, 1, n, id[x], id[y], val); } inline int qSon(int x) { return query(1, 1, n, id[x], id[x]+siz[x]-1); } inline void updSon(int x, int val) { update(1, 1, n, id[x], id[x]+siz[x]-1, val); } void dfs1(int x, int f, int d) { dep[x] = d, fa[x] = f, siz[x] = 1; int heavy=-1; for (rint i=head[x]; i; i=nex[i]) { int &y=to[i]; if (y==f) continue; dfs1(y, x, d+1); siz[x]+=siz[y]; if (siz[y]>heavy) son[x]=y, heavy=siz[y]; } } void dfs2(int x, int tp) { id[x]=++id[0], wt[id[0]]=w[x], top[x]=tp; if (!son[x]) return; dfs2(son[x], tp); // heavy son first for (rint i=head[x]; i; i=nex[i]) { int &y=to[i]; if (y==fa[x] || y==son[x]) continue; dfs2(y, y); // light son with new chain } } dfs1(root, 0, 1); dfs2(root, root); build(1, 1, n);
<br />spa
https://oi-wiki.org/graph/tree-misc/code
在頂點活動網 (Activity On Vertex network, AOV) 中,若不存在迴路,則全部活動可排列成一個線性序列,使得每一個活動的全部前驅活動都排在該活動的前面,咱們把此序列叫作拓撲序列 (Topological order),由 AOV 網構造拓撲序列的過程叫作拓撲排序 (Topological sort)。AOV 網的拓撲序列不是惟一的,知足上述定義的任一線性序列都稱做它的拓撲序列。htm
Kahn 算法:blog
將入度爲 0 的邊組成一個集合 $S$,每次從 $S$ 裏面取出一個頂點 $v$ (能夠隨便取) 放入 $L$,而後遍歷頂點 $v$ 的全部邊 $(u_1, v), (u_2, v), (u_3, v) \cdots$ 並刪除,判斷若是該邊的另外一個頂點在移除這一條邊後入度爲 0,那麼就將這個頂點放入集合 $L$ 中。排序
不斷地重複取出頂點。索引
最後當集合爲空後,就檢查圖中是否存在任何邊。若是有,那麼這個圖必定有環路,不然返回 $L$,$L$ 中順序就是拓撲排序的結果。隊列
經常使用隊列實現 $S$ 集合。時間複雜度 $O(V+E )$。
int ind[N], topo[N], cnt; queue<int> q; for (int i=1, a, b; i<=m; i++) scanf("%d%d", &a, &b), add(a, b), ++ind[b]; for (int i=1; i<=n; i++) if (!ind[i]) q.push(i); while (!q.empty()) { int t = q.top(), q.pop(); topo[++cnt] = t; for (int i=head[t]; i; i=nex[i]) { --ind[to[i]]; if (!ind[to[i]]) q.push(to[i]); } } if (cnt < n) printf("有環!")
<br />
在一個有向圖中,若是某兩點間都有互相到達的路徑,那麼稱中兩個點強連通,若是任意兩點都強連通,那麼稱這個圖爲強連通圖;一個有向圖的極大強連通子圖稱爲強連通份量。
一個強連通份量中的點必定處於搜索樹中同一棵子樹中。
Tarjan 算法:
low[]
表示這個點以及其子孫節點連的全部點中dfn最小的值.s[]
表示當前全部可能能構成是強連通份量的點.col[]
對強連通份量進行染色.v[to[k]]==false
說明不管如何這個點也不能與u構成強連通份量,由於它不能到達u.low[x]==dfn[x]
說明u點及u點之下的全部子節點沒有邊是指向u的祖先的了,即u點與它的子孫節點構成了一個最大的強連通圖即強連通份量.if (!dfn[i]) tarjan(i);
Tarjan一遍不能搜完全部的點,由於存在孤立點. 因此咱們要對一趟跑下來尚未被訪問到的點繼續跑Tarjan.均攤時間複雜度 $O(n)$.
int dfn[N], low[N], t, s[N], st; int col[N], ct; bool v[N]; void tarjan(int x) { dfn[x]=low[x]=++t, s[++st]=x, v[x]=true; for (int k=head[x]; k; k=nex[k]) { if (dfn[to[k]]) {if (v[to[k]]) low[x]=min(low[x], dfn[to[k]]); } else tarjan(to[k]), low[x]=min(low[x], low[to[k]]); } if (low[x]==dfn[x]) { col[x]=++ct, v[x]=false; while (s[st]!=x) col[s[st]]=ct, v[s[st--]]=false; --st; } } for (int i=1; i<=n; ++i) if (!dfn[i]) tarjan(i);
for (rint i=1; i<=n; ++i) ++cnt[col[i]]; for (rint i=1; i<=ct; ++i) if (cnt[i]>1) ++ans; printf("%d\n", ans);
縮點: 對於 貢獻具備傳遞性 的題,由於強連通份量中的每兩個點都是強連通的,能夠將一個強連通份量當作一個 超級點,而點權按題意來定.
POJ2186 Popular Cows: 告訴你有n頭牛,m個崇拜關係,而且崇拜具備傳遞性,若是a崇拜b,b崇拜c,則a崇拜c,求最後有幾頭牛被全部牛崇拜.
顯然一個強聯通份量內的全部點都是知足條件的,咱們能夠對整張圖進行縮點,而後就簡單了.
剩下的全部點都不是強連通的,如今整張圖就是一個DAG(有向無環圖).
那麼就變成一道水題了,由於這是一個有向無環圖,不存在全部點的出度都不爲零的狀況.
因此必然有1個及以上的點出度爲零,若是有兩個點出度爲零,那麼這兩個點確定是不相連的,即這兩圈牛不是互相崇拜的,因而此時答案爲零,若是有1個點出度爲0,那麼這個點就是被全體牛崇拜的.
這個點多是一個強聯通份量縮成的 超級點,因此應該輸出整個強聯通份量中點的個數.
/* 以上同 Tarjan 求強連通份量. */ int deg[N], cnt[N]; int tot=0, ans=0; for (int i=1; i<=n; ++i) { for (int k=head[i]; k; k=nex[k]) if (col[to[k]]!=col[i]) ++deg[col[i]]; ++cnt[col[i]]; } for (int i=1; i<=ct; ++i) if (!deg[i]) ++tot, ans=cnt[i]; if (!tot || tot>1) printf("0\n"); else printf("%d\n", ans);
無向圖.
特判:根節點若是有兩個及以上的兒子,那麼他也是割點.
int dfn[N], low[N], t, root; bool cut[N]; void tarjan(int x) { int flag=0; dfn[x]=low[x]=++t; for (int k=head[x]; k; k=nex[k]) { if (dfn[to[k]]) low[x]=min(low[x], dfn[to[k]]); else { tarjan(to[k]), low[x]=min(low[x], low[to[k]]); if (low[y]>=dfn[x]) { ++flag; if (x!=root || flag>1) cut[x]=true; } } } } for (int i=1; i<=n; ++i) if (!dfn[i]) tarjan(root=i); for (int i=1; i<=n; ++i) if (cut[i]) printf("%d ", i);
鄰接表存圖編號從2開始. 即開頭 head[0]=1;
.
int dfn[N], low[N], t; bool bdg[N<<1]; void tarjan(int x, int last) { dfn[x]=low[x]=++t; for (int k=head[x]; k; k=nex[k]) { if (dfn[to[k]]) if (i!=(last^1)) low[x]=min(low[x], dfn[to[k]]); else { tarjan(to[k], k), low[x]=min(low[x], low[to[k]]); if (low[y]>=dfn[x]) bdg[k]=bdg[k^1]=true; } } } for (int i=1; i<=n; ++i) if (!dfn[i]) tarjan(i, 0); for (int i=2; i<head[0]; i+=2) if (bdg[i]) printf("%d %d\n", to[i^1], to[i]);