**~~
Dilworth定理总结
**~~
定理内容:偏序集能划分成的最少的全序集个数等于最大反链的元素个数/最长反链=最小链覆盖
相关解释:
偏序:定义集合A中的一个二元关系≤,对于两个元素(a1,b1)和(a2,b2),可以定义(a1,b1)≤(a2,b2),当且仅当a1≤a2且b1≤b2。若二者有一不满足,则两元素不可比
满足以下三个条件时,(A,≤)是偏序集:
1.自反性:∀a∈A,a≤a
2.反对称性:∀a,b∈A,若a≤b,b≤a则a=b。
3.传递性:∀a,b,c∈A,若a≤b,b≤c,则a≤c。
全序集:设≤为非空集合A上的一个偏序关系,若对于集合∀a,b∈B都有b≤a或a≤b(即元素两两可比),就称(B,≤)为一个全序集。
反链:偏序集(B,≤)中任意元素两两不可比。
例题:
1.P1020 有一种存在缺陷导弹拦截系统:、它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。输入导弹依次飞来的高度。计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
第一问裸LIS。
对第二问,设每个元素为二元组(a,b) a为到达时间,b为高度。令偏序关系<=为 a1<a2 &&b1>=b2。问最少划分为多少全序集,也就是求最长反链长度,即LIS元素个数。
const int MAX_N=1e5+5;
int n,a[MAX_N],b[MAX_N];
int ans[MAX_N],len;
int LIS(int *a)
{
ans[1]=a[1],len=1;
repi(i,2,n){
if(a[i]>ans[len]) ans[++len]=a[i];
else ans[lower_bound(ans+1,ans+len,a[i])-ans]=a[i];
}
return len;
}
int LIS_2(int *a)
{
ans[1]=a[1],len=1;
repi(i,2,n){
if(a[i]>=ans[len]) ans[++len]=a[i];
else ans[upper_bound(ans+1,ans+len,a[i])-ans]=a[i];
}
return len;
}
int main()
{
int pos=0,x;
while(~si(x)) a[++pos]=x;
n=pos;
repi(i,1,n) b[pos--]=a[i];
printf("%d\n%d\n",LIS_2(b),LIS(a));
return 0;
}
2.P4298 给定一张DAG,选取几个点,要求在选定的任意两点x,y,不存在x->y或y->x的路径。问 1.最多选几个点 2.给出一种构造方案 3.在最多选择的情况下,哪些点可以被选
令两点的偏序关系为x≤y为y到x存在路径
问题即为求所给DAG的最长反链。(任意两点不存在到对方的路径)
最长反链=最小不可重链覆盖
对于DAG图,也可当作最小可重链覆盖/最大独立集。对二分图,最大独立集为最小点覆盖的补集,而二分图最小点覆盖=最大匹配,答案即为点数-最大匹配。
具体做法:先floyd求出传递闭包,拆点为二分图,x->y存在边,二分图中
x1->y2连边。最长反链=最小链覆盖=点数-最大匹配数
对第二问,构造最一最大独立集,可先构造一最小点覆盖。后求其补集即可。
具体做法:从右侧非匹配点出发dfs。右侧的点只能走非匹配边向左访问,左侧的点只能走匹配边向右访问。取左侧被 DFS 到的点,以及右侧没被 DFS 到的点,做集合 S,可以证明 S 是一个最小点覆盖。(此处不予证明)。最大独立集等于最小点覆盖的补集。也就是只要选出左侧没被 DFS 到的点和右侧被 DFS 到的点就行了。
对DAG图,令最大独立集为I,选出所有左右两侧均在I的点记作集合A,构成最长反链
即 左侧没被dfs到 和 右侧被dfs到 的点。
第三问:枚举选定一点作为最长反链的一点,删除与其具有偏序关系的点,求剩下的点的最长反链与初始的最长反链是否差1即可
const int MAX_N=2e2+5;
const int MAX_E=1e4+5;
const int inf=INT_MAX;
int n,m;
int g[MAX_N][MAX_N];
struct Edge{
int to,nxt;
};
struct Bipartite_matching{
int nl,nr;
Edge e[MAX_E<<1];
int head[MAX_N],tote;
int match[MAX_N];
bool used[MAX_N],vis[MAX_N],ban[MAX_N];
void init(int l,int r)
{
nl=l,nr=r,tote=0;
repi(i,1,nl+nr) head[i]=0;
}
void add_edge(int u,int v)
{
e[++tote].to=v,e[tote].nxt=head[u],head[u]=tote;
e[++tote].to=u,e[tote].nxt=head[v],head[v]=tote;
}
bool dfs(int u)
{
if(ban[u]) return false;
used[u]=true;
reps(u)if(!ban[e[i].to]){
int v=e[i].to,w=match[v];
if(!w||!used[w]&&dfs(w)){
match[u]=v,match[v]=u;
return true;
}
}
return false;
}
int cal()
{
int res=0; ms(match);
repi(i,1,nl)if(!ban[i]){
if(!match[i]){
ms(used);
if(dfs(i)) res++;
}
}
return res;
}
void dfs_2(int u,int flag)
{
vis[u]=true;
reps(u)if(!vis[e[i].to]&&((match[e[i].to]==u)==flag)) dfs_2(e[i].to,flag^1);
}
}bm;
void floyd(int n)
{
repi(k,1,n)repi(i,1,n)repi(j,1,n)
g[i][j]=(g[i][k]&&g[k][j])?1:g[i][j];
}
void init(int n)
{
repi(i,1,n)repi(j,1,n) g[i][j]=0;
}
int main()
{
si(n),si(m);
init(n);
repi(i,1,m){
int u,v; si(u),si(v);
g[u][v]=1;
}
floyd(n);
bm.init(n,n);
repi(i,1,n)repi(j,1,n)if(g[i][j]) bm.add_edge(i,j+n);
int ans=n-bm.cal();
printf("%d\n",ans);
ms(bm.vis);
repi(i,1,n)if(!bm.match[i+n]) bm.dfs_2(i+n,0);
repi(i,1,n) printf("%d",(!bm.vis[i]&&bm.vis[i+n])?1:0);
puts("");
repi(i,1,n){
ms(bm.ban);
int tot=n;
repi(j,1,n)if(g[i][j]||g[j][i]||i==j) bm.ban[j]=bm.ban[j+n]=true,tot--;
printf("%d",tot-bm.cal()==ans-1);
}
puts("");
return 0;
}
3.ICPC Latin American Regional Contests-A 给一些集合,现从每个集合中选出一些子集,使得被挑选的集合不是包含关系。
令偏序关系≤为包含关系,问题即为求最长反链元素个数。最长反链=最小不可重链覆盖。对所给集合拆点建二分图。对x≤y≤z,只连z-y和y-x的边。最后点数-最大匹配即可。(注意与上题区分,上题是在DAG图的情况下)
const int MAX_N=3e5+5;
const int MAX_E=5e6+5;
struct Edge{
int to,nxt;
};
struct Bipartite_matching{
int nl,nr;
Edge e[MAX_E<<1];
int head[MAX_N],tote;
int match[MAX_N];
bool used[MAX_N];
void init(int l,int r)
{
nl=l,nr=r,tote=0;
repi(i,1,nl+nr) head[i]=0;
}
void add_edge(int u,int v)
{
e[++tote].to=v,e[tote].nxt=head[u],head[u]=tote;
e[++tote].to=u,e[tote].nxt=head[v],head[v]=tote;
}
bool dfs(int u)
{
used[u]=true;
reps(u){
int v=e[i].to,w=match[v];
if(!w||!used[w]&&dfs(w)){
match[u]=v,match[v]=u;
return true;
}
}
return false;
}
int cal()
{
int res=0; ms(match);
repi(i,1,nl){
if(!match[i]){
ms(used);
if(dfs(i)) res++;
}
}
return res;
}
}bm;
map<string,int> nam;
int id=0;
map<vector<int>,int> rec;
int tot=0;
int cnt[(1<<10)+5];
int main()
{
repi(i,0,(1<<10)) cnt[i]=__builtin_popcount(i);
cin.tie(0),cout.tie(0);
int n; si(n);
repi(i,1,n){
int m; si(m);
vector<int> vec;
repi(j,1,m){
string s; cin>>s;
if(!nam[s]) nam[s]=++id;
vec.pb(nam[s]);
}
sort(vec.begin(),vec.end());
if(rec[vec]) continue;
int len=vec.size(),mask=(1<<len)-1;
for(int sub=mask;sub;sub=(sub-1)&mask){
vector<int> tmp;
repi(j,0,len-1)if(sub&(1<<j)) tmp.pb(vec[j]);
if(!rec[tmp]) rec[tmp]=++tot;
}
}
bm.init(tot,tot);
for(auto it:rec){
vector<int> vec=it.fi;
int len=vec.size(),mask=(1<<len)-1;
for(int sub=(mask-1)&mask;sub;sub=(sub-1)&mask){
if(cnt[mask]!=cnt[sub]+1) continue;
vector<int> tmp;
repi(j,0,len-1)if(sub&(1<<j)) tmp.pb(vec[j]);
bm.add_edge(it.se,rec[tmp]+tot);
}
}
printf("%d\n",tot-bm.cal());
return 0;
}
好像之前还遇到过一个说是要dilworth定理的题,翻回去看发现前置技能还差些,再说吧
留坑待填