1. 程式人生 > 資訊 >“淘寶”上架淘寶:18 個故事,單個售價 1 分錢

“淘寶”上架淘寶:18 個故事,單個售價 1 分錢

本文參考自cz_xuyixuan的支配樹blog

以下給出若干定義

  • 給定一張有向圖,定義起點為 \(r\)
  • 定義 \(x\)\(y\) 的支配點,即 \(x \in dom(y)\),當且僅當刪掉 \(x\) 後, 從 \(r\) 無法到達 \(y\),且 \(x != y\)
  • 定義 \(x\)\(y\) 的最近支配點,當且僅當,\(x \in dom(y)\)\(\forall w \in dom(y),w != x\)\(w \in dom(x)\),以下稱為 \(idom(y) = x\),亦稱為 \(x\)\(y\)最近支配點
  • 由定義可知,\(idom(x)\)
    \(x\) 的連邊可以構成一棵樹,稱為支配樹
  • 定義有向圖的 \(dfs\) 樹為 \(T\)
  • 以下所有大小關係,均指 \(dfn\) 的大小關係,即重定義 \(<\) 表示 \(dfn[x] < dfn[y].\)
  • 定義 \(x\)\(y\) 的半支配點,即\(sdom(y) = \min\{x|\exist x -> v_1 -> v_2 -> ..... v_k ->y,\forall i \in [1,k],v_i > y\}\),用人話講就是 \(x\) 為滿足存在一條除首尾外,經過的點都大於 \(y\) 的到達 \(y\) 的路徑的點中 \(dfs\)
    序最小的點
  • 因為起點 \(r\) 的支配點不存在,所以以下討論都預設不討論 \(r\)
  • 以下用 \(a \rightarrow b\)表示存在一條 \(a\)\(b\) 的路徑,不經過樹邊,\(a -> b\)表示只經過樹邊, \(a\)\(b\) 的所有路徑則統稱為 \((a,b) \in path\)

以下給出若干定理和引理

  • \(idom(u)\) 一定是 \(u\) 的祖先(1)

顯然

  • \(idom(u)\)一定是 \(sdom(u)\) 的祖先(2)

\(proof\) :考慮反證,若不是,則存在 \(r -> sdom(u) \rightarrow u\)

不經過 \(idom(u)\),矛盾

  • \(sdom(u)\) 一定是 \(u\) 的祖先(3)

\(proof\) : 考慮反證,首先 \(sdom(u)\) 可以是 \(fa(u)\) ,所以 \(sdom(u) \leq fa(u) < u\) ,若不是祖先,則必然是其他子樹,考慮在 \(dfs\) 的過程中 ,若 \((sdom(u),u) \in path\), 那麼在遍歷到 \(sdom(u)\) 的過程中,必然直接遍歷到 \(u\) ,與 \(sdom(u) \rightarrow u\)矛盾

定理 ( idom 與 sdom 關係定理)
定義: \(u\)\(sdom(w)\)\(w\) 的樹的路徑中(不包含 \(sdom(w)\)), \(sdom(u)\) 最小的點,那麼有

  • \(idom(w) = sdom(u)\) \((sdom(u) = sdom(w))\)(4)
  • \(idom(w) = idom(u)\) \((sdom(u) < sdom(w))\)(5)

\(proof\):首先我們考慮對於任意一對 \((u,x)\) 如何證明 \(idom(u) = x\),我們僅需證明兩點

  • \(x \in dom(u)\)
  • \(\forall v \in (x -> u), v != x\) , 有\(v \notin dom(u)\)

(4) 的證明

\(sdom(u) = sdom(w)\),我們考慮 \(sdom(u)\) 必然是支配點,假設存在一個點 \(x\) 使得 \(r -> x \rightarrow o -> w\), 且 \(x < u\),那麼 \(sdom(o) < sdom(u)\),矛盾

接著我們考慮假設有一個點 \(y\)\(sdom(u) -> u\)中,且是支配點,顯然和引理\((2)\)矛盾

(5) 的證明

先證\(idom(u) \in dom(w)\)

考慮反證,若刪掉 \(idom(u)\) 後,仍然存在 \((r,w) \in path\),那麼必然是 \(r -> x \rightarrow o -> w\),且 \(o > u,x < idom(u)\),且 \(sdom(o) = x < sdom(u)\),矛盾

再證不存在更近的支配點

依然考慮反證,假設存在這樣的點,不妨設為 \(y\),我們考慮將其刪去,繼續分類討論

  • (1),\(y \notin (u,w)\) 那麼因為 \(idom(u)\) 已經是最近的支配點,那麼必然存在 \((r,u) \in path\),又因為存在 \(u -> w\),那麼必然存在\((r,w) \in path\),矛盾
  • (2),\(y \in (u,w)\),\(idom(w)\) 一定是 \(sdom(w)\)的祖先,矛盾

那麼我們根據這個定理,只需求出 \(sdom\) ,就可以在 \(O(n\log n)\)的時間內,求出 \(idom\)

考慮 \(sdom\) 怎麼求

定理 (sdom 的另一種簡化定義)(6)

  • \(S1(w) = \{(v,w) \in E,v < w\}\)
  • \(S2(w) = \{sdom(u)|u > w,\exist(v,w),(u -> v) \in T \}\)
  • \(sdom(w) = \min\{v|v \in S1(w) \cup S2(w)\}\)

用人話說,就是我們考慮所有\((v,w) \in E\),如果\(v < w\),那麼\(v\)可以納入侯選集合,否則,我們考慮其所有祖先\(u\)滿足\(u > w\),\(sdom(u)\)可以納入侯選集合

證明:\(S1\)的證明是顯然的,我們考慮 \(S2\) 怎麼證,其實也是顯然的,我們考慮這麼一條路徑,\(sdom(u) -> u -> v -> w\),滿足條件

有了以上這些定理和引理,我們考慮具體的構造過程

構造過程

大體可以分成兩部分。

  • 得到 \(sdom\) 陣列
  • \(sdom\) 得到 \(idom\)

以下內容基本參考 cz_xuyixuan 的 blog,先膜拜

演算法基本流程

  • 初始化、跑一遍 \(DFS\) 得到 \(T\).
  • 按標號從大到小求出 \(sdom\)
  • 求出所有能確定的 \(idom\), 剩下的點記錄下和哪個點的 \(idom\) 是相同的
  • 按照標號從小到大再跑一次,得到所有點的 \(idom\)

第一步顯然

第二步和第三步可以一起做,我們可以考慮維護一個帶權並查集,每個點維護其到根 \(sdom\) 的最小值,按 \(dfs\) 序從大到小掃一遍,每當我們計算完 \(w\) ,我們可以尋找當前所有 \(sdom(u) = fa(w)\) 的點 \(u\) , 計算 \(\min\{sdom(v)| v \in (sdom(u)->u)\}\),這個可以用 \(\textstyle vector\) 然後清空掉,容易發現這樣維護的只有當前一條鏈,然後我們可以通過定理 (4), (5) ,來得到 \(idom(u) = sdom(u)\) 或者 \(idom(u) = idom(v)\),不過我們現在可能還不知道 \(idom(v)\) , 所以先打個標記

最後,從小到大掃一遍,得到 \(idom\) 陣列

程式碼如下:也就調了一百萬年吧

#include<bits/stdc++.h> 
using namespace std;
int read() {
	char c = getchar();
	int x = 0;
	while(c < '0' || c > '9')	c = getchar();
	while(c >= '0' && c <= '9')	x = x * 10 + c - 48,c = getchar();
	return x;
}
const int _ = 5e5 + 7;
int sdom[_],idom[_];
vector<int>E[_];
vector<int>O[_];
vector<int>T[_];
vector<int>G[_]; 
int par[_];
#define pb push_back
int n,m;int siz[_]; 
int dfn[_],dfncnt,isdfn[_];
int fa[_];int mn[_];
bool cmp(int u,int v) {
	return dfn[u] < dfn[v];
}
int Min(int u,int v) {
	if(cmp(u,v))	return u;
	return v;
}
int get(int u) {
	if (fa[u] == u)	return u;	
	int tmp = fa[u];	
	fa[u] = get(fa[u]);
	if(dfn[sdom[mn[u]]] > dfn[sdom[mn[tmp]]])		mn[u] = mn[tmp];
	return fa[u];
}
void dfs(int u) {
	dfn[u] = ++dfncnt;isdfn[dfncnt] = u;
	sdom[u] = u;
	for (auto v:E[u]) {
		if(!dfn[v]){
//			cout<<"E"<<' '<<u <<' '<<v<<'\n';
			par[v] = u;
			dfs(v);
		}
	}
}
void tree(int u) {
	siz[u] = 1;
	for (auto v:T[u]) {
//		cout << u << ' ' <<v<<"hxsb"<<'\n';
		tree(v);
		siz[u] += siz[v];
	}
}
int main() {
	n = read(),m = read();
	for (int i = 1; i <= m; ++i) {
		int u = read(),v = read();
		E[u].pb(v);O[v].pb(u);
	}
	E[0].pb(1);O[1].pb(0);
	dfs(0);
	for (int i = 1; i <= n; ++i)	fa[i] = i;
	for (int i = n + 1; i >= 1; --i) {
		int u = isdfn[i];
		for (auto v:O[u]) {
			if (cmp(v,u)){
				sdom[u] = Min(sdom[u],v);
			}
			else {
				get(v);				
				sdom[u] = Min(sdom[u],sdom[mn[v]]);
			}
		}
		G[sdom[u]].pb(u);
		mn[u] = u;
		for (auto v:E[u]) {
			if(dfn[v] > dfn[u] && par[v] == u) {
				fa[v] = u;
				get(v);
			}
		}
		for (auto v:G[par[u]])  {
//			if(v == 4)	cout<<<<'\n';
			get(v);
			int w = mn[v];
//			if(v == 4)	cout<<w<<'\n';
			if(sdom[w] == sdom[v])	idom[v] = sdom[v];
			else	idom[v] = w;
		}
		get(u);
		G[par[u]].clear();
	}
	for (int i = 1; i <= n + 1; ++i) {
		int u = isdfn[i];
		if (idom[u] == sdom[u])	continue;
		else	idom[u] = idom[idom[u]];
	}		
//	for (int i = 1; i <= n; ++i)	cout<<idom[i]<<' ';
//	cout<<'\n';
	for (int i = 1; i <= n; ++i)	T[idom[i]].pb(i);
	tree(0);
	for (int i = 1; i <= n; ++i)	printf("%d ",siz[i]);
	cout<<'\n';
	return 0;
}