【笔记】Dilworth定理

给定一个有向无环图,我们称 \(x,y\) 存在关系当且仅当存在 \(x\to y\) 或者 \(y \to x\) 的边。最长链为最大的集合使得其中任意两个元素存在关系,最长反链为最大的集合使得其中任意两个元素不存在关系。

Dilworth 定理:最长反链等于最小链覆盖。

最小链覆盖为用最少的链(可以相交)覆盖图的每一个点,我们传递闭包后即可转化为经典二分图模型:最小链划分。

如何求出具体方案?我们先求出链划分,然后先选取每条链的终点,只要存在两个点之间有关系,如 \(x\to y\),就将 \(y\) 沿着对应的链向起点走。可以证明重复这个过程可以得到答案。

听起来很复杂,但实现很简单。下面是 CF590E 的代码。

#define N 10000005 #define M 755 char s[N + M]; int idx, n, ch[N][2], fa[N], w[N], u[M], d[M][M], m, v[M], mat[M]; queue<int>q; bool dfs(int x){ 	rp(i, n)if(d[x][i] && !v[i]){ 		v[i] = 1; 		if(!mat[i] || dfs(mat[i])){mat[i] = x; return true;} 	}return false; } vector<int>c; int main() { 	read(n); 	rp(i, n){ 		u[i] = ++m; scanf(%s, s + m); 		int l = strlen(s + m), x = 0; 		rp(j, l){ 			int op = s[j + m - 1] - 'a'; 			if(!ch[x][op])ch[x][op] = ++idx; 			x = ch[x][op]; 		} 		w[x] = i, m += l; 	} 	if(ch[0][0])q.push(ch[0][0]); 	if(ch[0][1])q.push(ch[0][1]); 	while(!q.empty()){ 		int x = q.front(); q.pop(); 		if(!w[x])w[x] = w[fa[x]]; 		if(ch[x][0])fa[ch[x][0]] = ch[fa[x]][0], q.push(ch[x][0]); 		else ch[x][0] = ch[fa[x]][0]; 		if(ch[x][1])fa[ch[x][1]] = ch[fa[x]][1], q.push(ch[x][1]); 		else ch[x][1] = ch[fa[x]][1]; 	} 	rp(i, n){ 		int k = strlen(s + u[i]), x = 0; 		rp(j, k){ 			if(w[x])d[i][w[x]] = 1; 			x = ch[x][s[j + u[i] - 1] - 'a']; 		} 		if(w[fa[x]])d[i][w[fa[x]]] = 1; 	} 	rp(k, n)rp(i, n)rp(j, n)d[i][j] |= d[i][k] & d[k][j]; 	int ans = n; 	rp(i, n){ 		memset(v, 0, sizeof(v)); 		if(dfs(i))ans--; 	} 	printf(%d\n, ans); 	memset(v, 0, sizeof(v)); 	rp(i, n)v[mat[i]] = 1; 	rp(i, n)if(!v[i])c.pb(i); 	while(true){ 		memset(v, 0, sizeof(v)); 		go(x, c)rp(i, n)if(d[x][i])v[i] = 1; 		int p = 0; 		for(auto &x : c)while(v[x])x = mat[x], p = 1; 		if(!p)break; 	} 	go(x, c)printf(%d , x);el; 	return 0; }