日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

支配树学习思路/模板

發(fā)布時間:2023/12/20 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 支配树学习思路/模板 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

首先解釋什么是支配樹。
首先我們先看一下DAG,我們現(xiàn)在有一個DAG:

如果我們從1出發(fā),對于圖中的一個點ppp,在從1到ppp的不同路徑中,總有一些點是必經(jīng)的,例如圖中的8號點,我們發(fā)現(xiàn)在從1到8的不管是那一條路徑上,5都是必須經(jīng)過的。(當(dāng)然1號點和8號點也可以理解為必經(jīng)的)。對于每一個點,如果把該點向上最近的一個必經(jīng)點和該點連有向邊。我們就得到了這個圖的支配樹。
還是那這個DAG舉例,我們按照上述的方法連邊(紅線):

紅邊就是該DAG的支配樹。對于一個點ppp,從其到1的樹鏈上的每一個點,都是1到ppp的必經(jīng)點。
我們仔細觀察這個圖,我們發(fā)現(xiàn),對于一個點,他最近的必經(jīng)點(支配樹父親)都是他的所有入點的“LCA”。例如,5號點的入點V= { 6,2,3 },其”LCA”為1。同理8號點,入點的“LCA”為5。那么我們怎么求這個所謂的“LCA”呢?
我們都知道,“LCA”的概念僅存在于樹上,對于DAG,對于一個點集沒有所謂固定的“LCA”(因為一個點可能不止一個父親(入點))。但是對于上圖,我們可以考慮,如果一個點的所有入點向上的支配樹已經(jīng)建成,那么該點的最近必經(jīng)點就是這些入點在當(dāng)前已建支配樹上的LCA
有點繞。。。我們看例子,比如現(xiàn)在我們要確定5號點的最近必經(jīng)點。且V= { 6,2,3 }向上的支配樹已經(jīng)建成。那么此時圖是這樣的:

從圖上非常明顯的知道:V= { 6,2,3 }的在已建成的支配樹上的LCA為1。這時我們連邊<1, 5>

對于其他的點,同理。所有我們知道,我們在依次建立支配樹的邊時,必須保證該點的所有入點的支配樹都已建成。所以,我們必須先處理每個點的入點,在處理該點。。這不就是 拓撲排序么?我們可以按照拓撲順序處理完這個DAG,建成支配樹。下面看一個例題。
P2597 [ZJOI2012]災(zāi)難
相信這個題,已經(jīng)在各種大佬博客、題解中看了不止一遍了。我們?nèi)绻蒙鲜龇椒ńǔ稍撌澄锞W(wǎng)的支配樹。那么每個點的答案,就該點的支配樹子樹的大小了。
我們看代碼:首先建圖:

void add(int x, int y) {ver[++tot] = y;ne[tot] = he[x];he[x] = tot;verf[tot] = x;nef[tot] = hef[y];hef[y] = tot; } scanf("%d", &n); for (int i = 1; i <= n; i++) {int x;while(scanf("%d", &x), x){di[i]++;add(x, i);} }

可以看到,在建圖的時候,我們統(tǒng)計入度,并同時建出反圖(一會提到為什么)
然后我們拓撲排序,得到點的拓撲序:

void topu() {for (int i = 1; i <= n; i++) if (!di[i])q.push(i);while(q.size()){int now = q.front();q.pop();topp[++cnt] = now;//記錄拓撲序for (int i = he[now]; i; i = ne[i]){int y = ver[i];di[y]--;if (!di[y])q.push(y);}} }

然后我們我們按照拓撲順序建立支配樹:

void adda(int x, int y) {vera[++tot] = y;nea[tot] = hea[x];hea[x] = tot; } for (int i = 1; i <= n; i++) {int x = verf[hef[topp[i]]];//利用反圖,找到topp[i]點的入點for (int j = hef[topp[i]]; j; j = nef[j])x = lca(x, verf[j]);//遍歷所有入點,算出lcaadda(x, topp[i]);//聯(lián)邊建樹d[topp[i]] = d[x] + 1;f[topp[i]][0] = x;for (int j = 1; j <= 25; j++)f[topp[i]][j] = f[f[topp[i]][j-1]][j-1];//邊建樹便記錄f數(shù)組 }

此時hea系列數(shù)組記錄的就是支配樹信息。
最后我們dfs一下支配樹,記錄子樹大小就可以了。
下面是完整ac代碼:

#include <iostream> #include <cstdio> #include <queue> #include <algorithm> #include <cmath> #include <cstring> #include <string> #include <vector> #include <map> #include <cstdlib> #define ll long long using namespace std; const int N = 2e5+5; int tot, he[N], ver[N], ne[N]; int hef[N], verf[N], nef[N]; int tota, hea[N], vera[N], nea[N]; int f[N][30], d[N]; int di[N]; int n,m, cnt, topp[N]; queue<int> q; void add(int x, int y) {ver[++tot] = y;ne[tot] = he[x];he[x] = tot;verf[tot] = x;nef[tot] = hef[y];hef[y] = tot; } void adda(int x, int y) {vera[++tot] = y;nea[tot] = hea[x];hea[x] = tot; } void topu() {for (int i = 1; i <= n; i++) if (!di[i])q.push(i);while(q.size()){int now = q.front();q.pop();topp[++cnt] = now;for (int i = he[now]; i; i = ne[i]){int y = ver[i];di[y]--;if (!di[y])q.push(y);}} } int lca(int x, int y) {if (d[x] > d[y]) swap(x, y);for (int i = 25; i >= 0; i--){if (d[f[y][i]] < d[x]) continue;y = f[y][i];}if (x == y) return x;for (int i = 25; i >= 0; i--)if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];return f[x][0]; } int siz[N]; int dfs(int now) {siz[now] = 1;for (int i = hea[now]; i; i = nea[i]){siz[now] += dfs(vera[i]);}return siz[now]; } int main() {scanf("%d", &n);for (int i = 1; i <= n; i++){int x;while(scanf("%d", &x), x){di[i]++;add(x, i);}}topu();for (int i = 1; i <= n; i++){int x = verf[hef[topp[i]]];for (int j = hef[topp[i]]; j; j = nef[j])x = lca(x, verf[j]);adda(x, topp[i]);d[topp[i]] = d[x] + 1;f[topp[i]][0] = x;for (int j = 1; j <= 25; j++)f[topp[i]][j] = f[f[topp[i]][j-1]][j-1];}dfs(0);for (int i = 1; i <= n; i++)printf("%d\n", siz[i]-1);return 0; }

很好,我們現(xiàn)在探討完DAG的情況了,終于進入最普遍的支配樹的學(xué)習(xí)了。即:一般有向圖的支配樹與支配點(即必經(jīng)點)。
下面有一個有向圖:
(藍色邊為其dfs搜索樹的邊,為方便每個點的編號都是它的dfn)

這里我引入半支配點的概念:對于圖,若存在一個簡單路徑<x,y>。如果從x到y(tǒng)所經(jīng)歷的所有點(除了x,y)的時間戳都大于y的時間戳,我們認為x為y的半支配點
例如圖中的6號點,他的半支配點集為V = {4,7,8,1}。因為從這些點出發(fā)都可以找到一個簡單路徑到達6,且滿足上述條件(如圖紅邊所示):

對于每一個點,我們找到他的半支配點集中的dfn最小的一個,記做semi[x]semi[x]semi[x]
然后對于每一個<semi[x]semi[x]semi[x], xxx>連邊。(如圖紅邊所示)

為什么要這么做?因為我們?nèi)绻阉蟹撬阉鳂涞倪厔h去,鏈接<semi[x]semi[x]semi[x], xxx>,那么對于每個點,其支配性不變。而又因為,對于每個點xxxsemi[x]semi[x]semi[x]都是它搜索樹上的祖先
對于一個點xxx找出所有的邊(y,x)(y,x)(y,x)中對應(yīng)的yyy
dfn[y]<dfn[x]dfn[y]<dfn[x]dfn[y]<dfn[x]dfn[y]dfn[y]dfn[y]比當(dāng)前找到的semi[x]semi[x]semi[x]dfndfndfn小 那么就用semi[x]=ysemi[x]=ysemi[x]=y
dfn[y]>dfn[x]dfn[y]>dfn[x]dfn[y]>dfn[x]找打樹上yyy的一個祖先zzz 并且dfn[z]>dfn[x]dfn[z]>dfn[x]dfn[z]>dfn[x]比較semi[z]semi[z]semi[z]semi[x]semi[x]semi[x]決定是否用前者更新后者。
這時,我們發(fā)現(xiàn)圖變成了下面的樣子:

沒錯,它變成了一個DAG。結(jié)合上述定理(支配性不變)。我直接按照DAG的支配樹求法去求這個圖的支配樹。除此之外,但是我們有更好的辦法。
對于一個點的最近支配點,我們記為idom[x]idom[x]idom[x],我們知道,如果求出這個玩意,然后對于每個點建邊<idom[x],xidom[x],xidom[x],x>就是支配樹了。。那么我們怎么求idom[x]idom[x]idom[x]呢?
(摘自洛谷)
我們令PPP是從semi[x]semi[x]semi[x]xxx的搜索樹上路徑點集(不包括semi[x]semi[x]semi[x]) 而且zzzPPPsemi[z]semi[z]semi[z]最小的點。如果semi[z]=semi[x]semi[z]=semi[x]semi[z]=semi[x]那么idom[x]=semi[x]idom[x]=semi[x]idom[x]=semi[x]否則就是idom[x]=idom[z]idom[x]=idom[z]idom[x]=idom[z]
我們標(biāo)注出每個點的支配點(逗號后面):

最后連邊建成支配樹:

萬事大吉了。:

這里給出幾個性質(zhì):
1、每個點的semisemisemi是唯一 的。
2、一個點的semisemisemi一定是其dfs樹祖先。
3、x的半支配點不一定是x的支配點。
4、semi的深度大于idom的深度
5、如果節(jié)點U,V滿足,V是U的祖先。那么:可以 得出V是idom[U]idom[U]idom[U]的祖先,或者idom[U]idom[U]idom[U]idom[V]idom[V]idom[V]的祖先。
P5180 【模板】支配樹
下面是ac代碼:

// % everyone #include <cstdio> #include<iostream> #include<cstring> #include <map> #include <queue> #include <set> #include <cstdlib> #include <cmath> #include <algorithm> #include <vector> #include <string> #include <list> #include <bitset> #include <array> #include <cctype> #include <unordered_map> #include <time.h>namespace OI {double start_time = 0.0;void read_f(int flag = 0) { freopen("0.in", "r", stdin); if(!flag) freopen("0.out", "w", stdout); }void fast_cin() { std::ios::sync_with_stdio(false); std::cin.tie(); }void run_time() { std::cout << "\nESC in : " << ( clock() - start_time ) * 1000.0 / CLOCKS_PER_SEC << "ms" << std::endl; }void ct() { start_time = clock(); return; } } using namespace OI; template <typename T> bool bacmp(const T & a, const T & b) { return a > b; } template <typename T> bool pecmp(const T & a, const T & b) { return a < b; }#define ll long long #define ull unsigned ll #define _min(x, y) ((x)>(y)?(y):(x)) #define _max(x, y) ((x)>(y)?(x):(y)) #define max3(x, y, z) ( max( (x), max( (y), (z) ) ) ) #define min3(x, y, z) ( min( (x), min( (y), (z) ) ) ) #define pr make_pair #define pb push_back using namespace std;const int N = 2e5+5; int n, m; struct Node {int tot = 0;int ver[N<<1], he[N], ne[N<<1];void add(int x, int y){ver[++tot] = y;ne[tot] = he[x];he[x] = tot;} }a, b, c, d; int dfn[N], id[N], fa[N], cnt; void dfs(int u) {dfn[u] = ++cnt; id[cnt] = u;for (int i = a.he[u]; i; i = a.ne[i]){int y = a.ver[i];if (dfn[y]) continue;fa[y] = u; dfs(y);} } int semi[N], idom[N], bel[N], val[N]; int fi(int x) {if (x == bel[x]) return x;int fs = fi(bel[x]);if (dfn[semi[ val[bel[x] ] ] ] < dfn[ semi[val[x] ] ]) val[x] = val[bel[x]];return bel[x] = fs; } void tarjan() {for (int s = cnt; s>1; s--){int u = id[s];for (int i = b.he[u]; i; i = b.ne[i]){int y = b.ver[i];fi(y);if (dfn[semi[val[y]]] < dfn[semi[u]]) semi[u] = semi[val[y]];}c.add(semi[u], u);bel[u] = fa[u];u = fa[u];for (int i = c.he[u]; i; i = c.ne[i]){int y = c.ver[i];fi(y);if (semi[val[y]] == u) idom[y] = u;else idom[y] = val[y];}}for (int i = 2; i <= cnt; i++){int u = id[i];if (idom[u] != semi[u])idom[u] = idom[idom[u]];} } int ans[N]; void dfs_ans(int u) {ans[u] = 1;for (int i = d.he[u]; i; i = d.ne[i]){int y = d.ver[i];dfs_ans(y);ans[u] += ans[y];} } int main() {scanf("%d%d", &n, &m);for (int i = 1; i <= m; i++){int x, y; scanf("%d%d", &x, &y);a.add(x, y);b.add(y, x);}for (int i = 1; i <= n; i++) semi[i] = bel[i] = val[i] = i;dfs(1);tarjan();for (int i = 2; i <= n; i++) d.add(idom[i], i);dfs_ans(1);for (int i = 1; i <= n; i++) printf("%d ", ans[i]);puts("");return 0; }

總結(jié)

以上是生活随笔為你收集整理的支配树学习思路/模板的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。