您的位置:首页 > 其它

URAL 1533 Fat Hobbits

2016-02-11 17:01 316 查看

题意

构造最长反链

题解

先来回顾一下几个做法:

1. 求最长链——直接DP,然后还可能可以用二分优化

2. 求最小反链覆盖——类似拓扑排序,每次把度为0的点找出来删掉即可

3. 求最小链覆盖——即是在求路径覆盖,把每个点拆成i_0,i_1,原图i->j(注意要补全所有关系),则连i_0->j_1,

(点数-最大匹配数)即为答案。构造即为把匹配边“对回去”“连起来”

4. 求最长反链——在3.那个二分图中,求一个最小点覆盖集,则i被取出当且仅当i_0,i_1均不在这个点覆盖集中。

证明:

(1)求出的点集一定满足反链。证明:如果选出的某两个点之间有边,则不符合覆盖集定义,矛盾。

(2)i_0与i_1不可能同时在点覆盖集中。证明:若如此,则|反链|>n-|最小点覆盖|=n-|最大匹配|=|最小链覆盖|=|最长链|,矛盾。

(注意3和4需要把偏序关系补全,否则会出现问题,例如1->2->3)

然后再来回顾一下二分图求最小点覆盖

1. 求最大匹配

2. 对于左边所有的未配点,做增广,并标记(此时增广一定失败,否则就不是最大匹配了)

3. 左边所有的未标记点和右边所有的已标记点即为答案。

原因:

(1) 观察所有未匹配边,因为未配边不可能两边都是未配点(否则与最大匹配矛盾)

a. 左边是未配点,此时左边的点会被增广,右边的匹配点会被标记,由(3)会被覆盖。

b. 右边是未配点,那么左边一定是已配点,这种边不可能作为增广的最后一条边(否则增广路被找到),所以左右两边点都不会被标记,由(3)会覆盖

(2) 观察所有匹配边,要么在(3)中被标记,此时右边点被标记,要么左边的点没有标记,由(3)会被覆盖

同时,所有匹配边仅有一侧被覆盖,故此时|点覆盖集|=|最大匹配|,满足定理,故是合法最小覆盖集。

code

#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cassert>
using namespace std;

const int maxn=110;

int n;

int G[maxn][maxn];

int left[maxn],right[maxn];
int S[maxn],T[maxn];
bool match(int u){
if(S[u]) return 0;
S[u]=1;
for(int v=1;v<=n;++v)if(G[u][v]){
if(T[v]) continue;
T[v]=1;
if(!left[v] || match(left[v])){
left[v]=u;
right[u]=v;
return 1;
}
}
return 0;
}

int main(){
//  freopen("in.txt","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) scanf("%d",&G[i][j]);
for(int k=1;k<=n;++k) for(int i=1;i<=n;++i)if(G[i][k]) for(int j=1;j<=n;++j)if(G[k][j]) G[i][j]=1;
int res=0;
memset(left,0,sizeof left);
memset(right,0,sizeof right);
for(int i=1;i<=n;++i)if(!right[i]){
memset(S,0,sizeof S);
memset(T,0,sizeof T);
if (match(i)) ++res;
}
printf("%d\n",n-res);

memset(S,0,sizeof S);
memset(T,0,sizeof T);
for(int i=1;i<=n;++i)if(!right[i]) match(i);

int res2=0;
for(int i=1;i<=n;++i)if(!(!S[i] || T[i])){
printf("%d%c",i,(++res2)==n-res ? '\n' : ' ');
}

//  for(;;);

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息