最小链覆盖——Dilworth定理

Dilworth定理

Dilworth定理,一言以蔽之,偏序集能划分成的最少的全序集个数等于最大反链的元素个数。——————litble

狄尔沃斯定理(Dilworth’s theorem)亦称偏序集分解定理,是关于偏序集的极大极小的定理,该定理断言:对于任意有限偏序集,其最大反链中元素的数目必等于最小链划分中链的数目。此定理的对偶形式亦真,它断言:对于任意有限偏序集,其最长链中元素的数目必等于其最小反链划分中反链的数目,由偏序集P按如下方式产生的图G称为偏序集的可比图:G的节点集由P的元素组成,而e为G中的边,仅当e的两端点在P中是可比较的,有限全序集的可比图为完全图——————百科
二 偏序集中的概念

链 : D 中的一个子集 C 满足 C 是全序集 及C中所有元素都可以比较大小

反链 : D 中的一个子集 B 满足 B 中任意非空子集都不是全序集 即所有元素之间都不可以比较大小

链覆盖 : 若干个链的并集为 D ,且两两之间交集为 ∅

反链覆盖 : 若干个反链的并集为 D ,且两两之间交集为∅

最长链 : 所有链中元素个数最多的 (可以有多个最长链)

最长反链 : 所有反链中元素个数最多的 (可以有多个最长反链

偏序集高度 : 最长链的元素个数
偏序集宽度 : 最长反链中的元素个数

最小链覆盖(使链最少)= 最长反链长度 = 偏序集宽度
最小反链覆盖=最长链长度=偏序集深度


Dilworth定理在序列中的应用

洛谷P1020 导弹拦截
最小链覆盖——Dilworth定理_第1张图片
思路:
题目中说以后每一发都不能高于前一发,我们能拦截的导弹高度是非递增序列,第一问显然就是最大非递增序列的长度,因为这里范围比较大,所以我们单调队列+二分优化DP。
第二问要最少配备多少套拦截系统,就是问这个序列最少可以划分为多少个非递增序列,根据Dilworth定理,我们只需求最长上升子序列的长度就是答案。

int a[N];
int d[N];
int main(){
    int tot = 0;
    while(scanf("%d",&a[++tot]) == 1){}
    tot --;
    int len = 1;
    d[1] = a[1];
    rep(i,2,tot){
        if(a[i] <= d[len]) d[++len] = a[i];
        else {
            int pos = upper_bound(d +1,d + len + 1,a[i],greater<int>()) - d;
            d[pos] = a[i];
        }
    }
    cout << len<<endl;
    d[1] = a[1];
    len = 1;
    rep(i,2,tot){
        if(a[i] > d[len]) d[++len] = a[i];
        else {
            int pos = lower_bound(d+1,d +len + 1,a[i]) - d;
            d[pos] = a[i];
        }
    }
    cout << len;
}

Codeforce 1296 E
给你一个字符串,然后你可以数字给每个位置的字符染色,只有相邻的颜色不同的字符才可以交换位置。问你是否最小用多少颜色使得这个字符串经过数次交换后呈字典序从小到大分布。然后输出染色的结果。
思路:
题目就是问你最少用多少个最长不下降子序列凑成这个序列。根据Dilworth定理可得,是最大反链的个数,即最长下降子序列的长度。然后构造每个字符的颜色就是当前以它为结尾的字符串中最长下降子序列的长度。

int r[26];
int vis[N];
int main(){
    memset(vis,0,sizeof vis);
    int n;
    cin >> n;
    string s;
    cin >> s;
    int M = -INF;
    for(int i = 0;i < n;++i){
        int d = 0;
        for(int j = s[i] - 'a' + 1;j < 26;++j){
            d = max(d,r[j]);
        }
        r[s[i]-'a'] = d + 1;
        vis[i] = d + 1;
        M = max(M,d + 1);
    }
    cout << M<<endl;
    for(int i = 0;i < n;++i) cout << vis[i] <<' ';
}

牛客挑战赛36 C-纸飞机
最小链覆盖——Dilworth定理_第2张图片
思路:
题意显然问去除一个数后剩余的序列最少可以被多少个严格递减序列组成。
根据Dilworth定理,我们求反链的最大长度,即不严格递减序列的最大长度。
如果我们每删去一个数求一次肯定会超时,这样想,我们求出原来序列的最长不递减序列,每删去一个数,是否会影响原来序列的最长不递减序列的长度?

如果这个数不在原来的最长不递减序列中或者这个数所在的位置不唯一(1 3 2 4),则并不会影响原来的结果,而如果这个数在里面且唯一,则答案会减一。

那如果判断呢?
由于范围比较大, n 2 n^2 n2肯定超时,所以我们用树状数组优化, d p [ i ] dp[i] dp[i]代表着以 i i i结尾的最大长度,假设所谓最大长度为 a n s ans ans。求完后,我们倒序判断一个数是否在所求的最长不递减序列中。我们增加辅助数组 a x ax ax a x [ i ] ax[i] ax[i]代表最长序列中第 i i i个位置的数的最大值, v i s [ i ] vis[i] vis[i]表示第 i i i个数是否在最长序列中。
首先 d p [ i ] = a n s dp[i]=ans dp[i]=ans的数肯定在最长序列中,然后对于第 i i i个数字,如果 a [ i ] < = a x [ d p [ i ] + 1 ] a[i]<=ax[dp[i]+1] a[i]<=ax[dp[i]+1],那么说明第 i i i个数在最长序列中,我们更新最大的 a x [ d p [ i ] ] ax[dp[i]] ax[dp[i]]值,并开一 n u m num num数组统计最长序列中每个位置的数有几个。

最后判断输出即可。

int n;
int dp[N];
int c[N],ax[N];
int a[N],b[N];
bool vis[N];int num[N];
void modify(int x,int y){
    for(;x <= n;x += x&(-x)){
        c[x] = max(c[x],y);
    }
}
int getMax(int x){
    int ans = 0;
    for(;x;x -= x&(-x)){
        ans = max(ans,c[x]);
    }
    return ans;
}
int main(){
    n = read();
    rep(i,1,n) a[i] = b[i] = read();
    sort(b + 1,b + n + 1);
    int m = unique(b + 1,b + n + 1) - b - 1;
    rep(i,1,n) a[i] = lower_bound(b+1,b+m+1,a[i]) - b;
    int ans = 0;
    rep(i,1,n){
        dp[i] = getMax(a[i]) + 1;
        modify(a[i],dp[i]);
        ans = max(ans,dp[i]);
    }
    cout <<ans << endl;
    ax[ans+1] = INF;
    fori(i,n,1){
        if(a[i] <= ax[dp[i]+1]){
            vis[i] = 1;
            num[dp[i]] ++;
            ax[dp[i]] = max(ax[dp[i]],a[i]);
        }
    }
    rep(i,1,n){
        if(num[dp[i]] == 1&&vis[i]) cout<<ans-1<<' ';
        else cout<<ans<<' ';
    }
}


Dilworth定理在DAG中的应用

DAG中,有如下的一些定义和性质:

链:一条链是一些点的集合,链上任意两个点x, y,满足要么 x 能到达 y ,要么 y 能到达 x 。

反链:一条反链是一些点的集合,链上任意两个点x, y,满足 x 不能到达 y,且 y 也不能到达 x。

一个定理:最长反链长度 = 最小链覆盖(用最少的链覆盖所有顶点)

对偶定理:最长链长度 = 最小反链覆盖

那么我们要求出的就是这个有向无环图的最小链覆盖了。
最小链覆盖即路径可以相交的最小路径覆盖。

\qquad \qquad\qquad\qquad\qquad ————by Eirlys_North

**DAG的最小路径覆盖**
定义:在一个有向图中,找出最少的路径,使得这些路径经过了所有的点。

最小路径覆盖分为最小不相交路径覆盖和最小可相交路径覆盖。

最小不相交路径覆盖:每一条路径经过的顶点各不相同。如图,其最小路径覆盖数为3。即1->3>4,2,5。

最小可相交路径覆盖:每一条路径经过的顶点可以相同。如果其最小路径覆盖数为2。即1->3->4,2->3>5。

特别的,每个点自己也可以称为是路径覆盖,只不过路径的长度是0。
\qquad \qquad\qquad\qquad\qquad ——justPassBy
最小链覆盖——Dilworth定理_第3张图片
对于DAG最小不相交路径覆盖有个定理
有向无环图G的最小路径点覆盖包含的路径条数,等于 n n n减去拆点二分图G2的最大匹配数。

而对于DAG最小可相交路径点覆盖:
我们把图中所有间接连通的点在原图中给他连一条边让它直接连通,那么这个问题就变成了上述DAG最小不相交路径点覆盖了。所以我们先对有向图传递闭包(偏序关系补全),然后 n 2 n^2 n2建立二分图,最后求最大匹配即可。

———————————————————————————————————
bzoj1143 祭祀

最小链覆盖——Dilworth定理_第4张图片
思路:

题目很显然让我们求一个最大的集合,集合中的点两两之间不能相互到达,这显然就是让求最大反链的长度,最长反链的长度 = 最小链覆盖。
即DAG最小可相交路径覆盖,先传递闭包,然后拆点二分图最大匹配即可。

int w[220][220];
int head[220],tot;
bool vis[205];
int match[205];
struct Edge{
    int next;
    int to;
}edge[N];
inline void add(int from,int to){
    edge[++tot].next = head[from];
    edge[tot].to = to;
    head[from] = tot;
}
void floyd(int n){
    rep(k,1,n){
        rep(i,1,n){
            rep(j,1,n){
                w[i][j] |= w[i][k]&w[k][j];
            }
        }
    }
}
bool dfs(int x){
    for(int i = head[x];i;i = edge[i].next){
        int y = edge[i].to;
        if(!vis[y]){
            vis[y] = 1;
            if(!match[y]||dfs(match[y])){
                match[y] = x;return true;
            }
        }
    }
    return false;
}
int main(){
    int n = read(),m = read();
    rep(i,1,m){
        int u = read(),v = read();
        w[u][v] = 1;
    }
    floyd(n);//传递闭包
    rep(i,1,n){
        rep(j,1,n){
            if(w[i][j]) add(i,j+n);
        }
    }
    int ans = 0;
    rep(i,1,n){//二分图最大匹配
        memset(vis,0,sizeof vis);
        if(dfs(i)) ans ++;
    }
    cout <<n - ans<<endl;
}

本文查阅诸多网上博客资料所写,如有冒犯请留言。

你可能感兴趣的:(最小链覆盖——Dilworth定理)