BZOJ 1143 祭祀river(最长反链长度)

1143: [CTSC2008]祭祀river

Time Limit: 10 Sec  Memory Limit: 162 MB
Submit: 3835  Solved: 1970
[Submit][Status][Discuss]

Description

  在遥远的东方,有一个神秘的民族,自称Y族。他们世代居住在水面上,奉龙王为神。每逢重大庆典, Y族都

会在水面上举办盛大的祭祀活动。我们可以把Y族居住地水系看成一个由岔口和河道组成的网络。每条河道连接着

两个岔口,并且水在河道内按照一个固定的方向流动。显然,水系中不会有环流(下图描述一个环流的例子)。

  由于人数众多的原因,Y族的祭祀活动会在多个岔口上同时举行。出于对龙王的尊重,这些祭祀地点的选择必

须非常慎重。准确地说,Y族人认为,如果水流可以从一个祭祀点流到另外一个祭祀点,那么祭祀就会失去它神圣

的意义。族长希望在保持祭祀神圣性的基础上,选择尽可能多的祭祀的地点。

Input

第一行包含两个用空格隔开的整数N、M,分别表示岔口和河道的数目,岔口从1到N编号。

接下来M行,每行包含两个用空格隔开的整数u、v,

描述一条连接岔口u和岔口v的河道,水流方向为自u向v。

N≤100M≤1000

Output

第一行包含一个整数K,表示最多能选取的祭祀点的个数。

Sample Input

4 4
1 2
3 4
3 2
4 2

Sample Output

2
【样例说明】
在样例给出的水系中,不存在一种方法能够选择三个或者三个以上的祭祀点。包含两个祭祀点的测试点的方案有两种:
选择岔口1与岔口3(如样例输出第二行),选择岔口1与岔口4。
水流可以从任意岔口流至岔口2。如果在岔口2建立祭祀点,那么任意其他岔口都不能建立祭祀点
但是在最优的一种祭祀点的选取方案中我们可以建立两个祭祀点,所以岔口2不能建立祭祀点。对于其他岔口
至少存在一个最优方案选择该岔口为祭祀点,所以输出为1011。

HINT

 

Source

 

[Submit][Status][Discuss]

思路: 该题实质就是求一个最长反链长度。

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

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

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

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

最小链覆盖其实就是最小可相交路径覆盖。

有向无环图最小不相交路径覆盖

 把原图中的每个点V拆成Vx和Vy,如果有一条有向边A->B,那么就加边Ax-By。这样就得到了一个二分图,最小路径覆盖=原图的节点数-新图最大匹配。

        简单证明:一开始每个点都独立的为一条路径,总共有n条不相交路径。我们每次在二分图里加一条边就相当于把两条路径合成了一条路径,因为路径之间不能有公共点,所以加的边之间也不能有公共点,这就是匹配的定义。所以有:最小路径覆盖=原图的节点数-新图最大匹配。

有向无环图最小可相交路径覆盖

先floyd求出连通性,这样最小可相交路径覆盖就可以转化成最小不可相交路径覆盖。

代码:

#include<bits/stdc++.h>

using namespace std;
const int N =205;
int girl[N],boy[N];
int vis[N];
int mp[N][N];
vector<int >ve[N];

int n,m;

int dfs(int u)
{
    for(int i=0;i<ve[u].size();i++){
        int v=ve[u][i];
        if(!vis[v]){
            vis[v]=1;
            if(girl[v]==-1||dfs(girl[v])){
                girl[v]=u;
                boy[u]=v;
                return 1;
            }
        }
    }
    return 0;
}

void xiongyali()
{
    int cnt=0;
    memset(girl,-1,sizeof(girl));
    memset(boy,-1,sizeof(boy));
    for(int i=1;i<=n;i++){
        memset(vis,0,sizeof(vis));
        if(dfs(i)) cnt++;
    }
    printf("%d\n",n-cnt);
    return ;
}

int main()
{
    int u,v;
    scanf("%d %d",&n,&m);

    for(int i=1;i<=m;i++){
        scanf("%d %d",&u,&v);
        mp[u][v]=1;
    }

    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            for(int k=1;k<=n;k++){
                if(mp[i][k]&&mp[k][j]) mp[i][j]=1;
            }
        }
    }

    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(i==j) continue;
            if(mp[i][j]) ve[i].push_back(j);
        }
    }

    xiongyali();
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值