[Dilworth] Dilworth学习笔记

Dilworth定理/狄尔沃斯定理
是偏序集相关的定理
但是在DAG里面用得也挺活的

Dilworth定理在DAG里的应用

链:一个点集 S S S,满足 ∀ u , v ∈ S \forall u,v\in S u,vS u u u能到达 v v v,或 v v v能到达 u u u
反链:一个点集 P P P,满足 ∀ u , v ∈ P \forall u,v\in P u,vP u u u不能到达 v v v,且 v v v不能到达 u u u

最小链覆盖:用最少的链覆盖整张图
最大反链长度:使反链最长

最大反链长度=最小链覆盖
可以理解为,补图中最长链长度即为最小链覆盖
补图中最长链长度就是最大反链长度

[CTSC2008]祭祀
求最小路径覆盖数/最长反链长度
路径可相交

最小路径覆盖数的算法:

  1. 对于不相交路径:
    裂开原图中的点,一分为二,跑二分图,最大匹配数与原图节点数的差值即为最小覆盖数
    :一条匹配边就将两条路径合成一条路径,总路径树-1
  2. 对于相交路径:
    利用传递闭包绕过不必经过的点(另外路径的点),转化为不相交路径

先来一波不输出方案的

#include<bits/stdc++.h>
using namespace std;
#define in Read()
int in{
	int i=0,f=1;char ch=0;
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') ch=getchar(),f=-1;
	while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int N=1e3+5;
int n,m,cnt;
int vis[N],tim,mat[N];
bool edge[N][N];
vector<int>G[N];

void floyd(){
    for(int k=1;k<=n;++k)
        for(int u=1;u<=n;++u)
            for(int v=1;v<=n;++v)
                edge[u][v]|=edge[u][k]&edge[k][v];
}

bool DFS(int u){
    for(int e=0;e<G[u].size();++e){
        int v=G[u][e];
        if(vis[v]!=tim){
            vis[v]=tim;
            if(mat[v]==-1||DFS(mat[v])){
                mat[v]=u;
                return true;
            }
        }
    }
    return false;
}

int main(){
    n=in,m=in;
    for(int i=1;i<=m;++i){
        int u=in,v=in;
        edge[u][v]=true;
    }
    floyd();
    for(int u=1;u<=n;++u)
        for(int v=1;v<=n;++v)
            if(edge[u][v])
                G[u].push_back(v+n);
    memset(mat,-1,sizeof(mat));
    for(int i=1;i<=n;++i){
        ++tim;
        if(DFS(i)) ++cnt;
    }
    printf("%d\n",n-cnt);
    return 0;
}

第一行输出一种可行方案
第二行输出可行方案的并集

但是不知道为什么
很玄的推出了第一行是求最大独立集
二分图的最小独立集算法:

对非匹配点DFS,做假的增广
标记搜到的点
取左边未标记点和右边标记点
这些点构成最小点覆盖
取反最大独立集

考虑第二行
考虑每个点
删去它和它能到的点,求一次最长反链
如果最长反链长度-1
可以说明该点可以替换原最长反链中的点,加上该点可以形成原最长反链
于是该点存在于一种方案中

#include<bits/stdc++.h>
using namespace std;
#define in Read()
int in{
	int i=0,f=1;char ch=0;
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') ch=getchar(),f=-1;
	while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int N=3e3+5;
int n,m;
int vis[N],tim,mat[N];
bool edge[N][N],to[N],ban[N],tag[N];
vector<int>G[N];

void floyd(){
    for(int k=1;k<=n;++k)
        for(int u=1;u<=n;++u)
            for(int v=1;v<=n;++v)
                edge[u][v]|=edge[u][k]&edge[k][v];
}

bool DFS(int u){
    if(ban[u]) return false;
    for(int e=0;e<G[u].size();++e){
        int v=G[u][e];
        if(vis[v]!=tim&&!ban[v]){
            vis[v]=tim;
            if(!mat[v]||DFS(mat[v])){
                to[u]=true;
                mat[v]=u;
                return true;
            }
        }
    }
    return false;
}

void DFS2(int u){
    if(tag[u]) return;
    tag[u]=true;
    for(int e=0;e<G[u].size();++e){
        int v=G[u][e];
        if(!tag[v]) tag[v]=true,DFS2(mat[v]);
    }
}

int main(){
    n=in,m=in;
    for(int i=1;i<=m;++i){
        int u=in,v=in;
        edge[u][v]=true;
    }
    floyd();
    for(int u=1;u<=n;++u)
        for(int v=1;v<=n;++v)
            if(edge[u][v])
                G[u].push_back(v+n);
    memset(mat,0,sizeof(mat));
    int cnt=n;
    for(int i=1;i<=n;++i){
        ++tim;
        cnt-=DFS(i);
    }
    printf("%d\n",cnt);

    for(int i=1;i<=n;++i)
        if(!to[i]) DFS2(i);
    for(int i=1;i<=n;++i)
        printf("%d",tag[i]&&!tag[i+n]);
    putchar('\n');

    for(int i=1;i<=n;++i){
        memset(mat,0,sizeof(mat));
        memset(ban,0,sizeof(ban));
        memset(to,0,sizeof(to));
        int tmp=0;

        for(int j=1;j<=n;++j){
            if(edge[i][j]||edge[j][i]||i==j)
                ban[j]=true,ban[j+n]=true;
            else ++tmp;
        }

        for(int j=1;j<=n;++j){
            ++tim;
            tmp-=DFS(j);
        }

        printf("%d",tmp==cnt-1);
    }
    putchar('\n');

    return 0;
}

Dilworth定理在偏序集中的应用

偏序集指一个集合中元素两两可以比较
当然我们可以把偏序集中的元素放到DAG中去理解

导弹拦截
菜鸡的噩梦啊

第一问求最长非递增序列长度,DP
考虑 O ( n log ⁡ n ) O(n\log n) O(nlogn)做法,用到单调队列+二分优化
(话说zgs给我们看的单调队列只是个皮毛,还是要自己学学)

第二问求最少划分成多少非递增序列
考虑Dilworth定理
求最大上升子序列长度
当然还是单调队列+二分优化

二分
lower_bound带等号,upper_bound不带等号
返回值-起始值得到数组下标
greater<int>()求小于,反之求大于

第一问,不带等号,求小于
第二问,带等号,求大于

#include<bits/stdc++.h>
using namespace std;

const int N=1e5+5;
int n,a[N],len,q[N];

int main(){
    while(scanf("%d",&a[++n])==1){}
    --n;
    len=1;
    q[1]=a[1];
    for(int i=2;i<=n;++i){
        if(a[i]<=q[len]) q[++len]=a[i];
        else{
            int pos=upper_bound(q+1,q+len+1,a[i],greater<int>())-q;
            q[pos]=a[i];
        }
    }
    printf("%d\n",len);

    q[1]=a[1];
    len=1;
    for(int i=2;i<=n;++i){
        if(a[i]>q[len]) q[++len]=a[i];
        else{
            int pos=lower_bound(q+1,q+len+1,a[i])-q;
            q[pos]=a[i];
        }
    }
    printf("%d\n",len);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值