【bzoj1143: [CTSC2008]祭祀river】有向无环图的最长反链

1143: [CTSC2008]祭祀river

Time Limit: 10 Sec   Memory Limit: 162 MB
Submit: 3192   Solved: 1632
[ Submit][ Status][ Discuss]

Description

  在遥远的东方,有一个神秘的民族,自称Y族。他们世代居住在水面上,奉龙王为神。每逢重大庆典, Y族都
会在水面上举办盛大的祭祀活动。我们可以把Y族居住地水系看成一个由岔口和河道组成的网络。每条河道连接着
两个岔口,并且水在河道内按照一个固定的方向流动。显然,水系中不会有环流(下图描述一个环流的例子)。

 

  由于人数众多的原因,Y族的祭祀活动会在多个岔口上同时举行。出于对龙王的尊重,这些祭祀地点的选择必
须非常慎重。准确地说,Y族人认为,如果水流可以从一个祭祀点流到另外一个祭祀点,那么祭祀就会失去它神圣
的意义。族长希望在保持祭祀神圣性的基础上,选择尽可能多的祭祀的地点。

Input

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

含两个用空格隔开的整数u、v,描述一条连接岔口u和岔口v的河道,水流方向为自u向v。 N ≤ 100 M ≤ 1 000

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。


icpc上居然出了这个原题,就爆炸了,回来补一补二分图和有向无环图的东西。


1、二分图最大匹配

匈牙利算法O(V*E)的复杂度内可求出二分图的最大匹配

2、二分图最小点覆盖

找到点数最少的一个点集,使得二分图里的所有边至少有一个端点是在该点集中

最小点覆盖=最大匹配

3、二分图最小边覆盖

找到边数最少的一个边集,使得二分图里的所有点至少是一条边的端点

最小边覆盖=图中点的个数-最大匹配

证明:把最大匹配的边都加入,则剩下的点之间都没有边,那么再把剩下的点都连接起来,边数=最大匹配+图中点的个数-2*最大匹配=图中点的个数-最大匹配

4、二分图最大独立集

找到一个点数最大的点集,使得点集里的所有点对之间在原图里没有边。

最大独立集=图中点的个数-最大匹配

证明:把所有点加入点集中,最大独立集问题就转化为删去最少的点使得最终的点集是个独立集,那么 最大独立集=图中点的个数-最小点覆盖=图中点的个数-最大匹配

最小不相交路径覆盖:每一条路径经过的顶点各不相同。如,最小路径覆盖数为31->3>425

最小可相交路径覆盖:每一条路径经过的顶点可以相同。如,最小路径覆盖数为21->3->42->3>5

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

用最少的不相交路径覆盖所有顶点。

将有向无环图中的各点拆点,i点拆为ai,bi  ,对于一条边:i->j,转化为 ai->bj, 便可把有向无环图转化为二分图。

有向无环图最小不相交路径覆盖=图中点的个数-最大匹配

证明:刚开始每个点为一条路径,则开始时有n条路径,对于二分图中的每一条匹配边就代表把两条路径变为一条路径,则最后的答案就是图中点的个数-最大匹配

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

用最少的可相交路径覆盖所有顶点。

处理出有向无环图中所有点的到达情况,拆点,若i点可经过一条路径到达j点,那么连一条ai->bj 的边,此时有向图边转化为二分图,问题也就转化为5、有向无环图最小不相交路径覆盖。

在有向无环图中,有如下的一些定义和性质:

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

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

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

对偶定理:最长链长度 = 最小反链覆盖 ///????这个是什么意思没看懂,有人看懂了评论下

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


经过上面一些乱七八糟的介绍,可以知道,这道题目就是求最长反链长度,就是求最长反链长度,就是求有向无环图最小可相交路径覆盖,见(6)

求各点的联通性可以用Floyd 也可以直接搜。

#include<cstdio>
#include<cstring>
#define M 1005
#define N 105
using namespace std;
int k,K,fir[N],Fir[N],n,m,l,r,sign[N],link[N],vis[N];
struct he{
    int r,nx;
}A[N*N],a[M];
void add(int l,int r){
    k++;a[k].r=r;a[k].nx=fir[l];fir[l]=k;
}
void Add(int l,int r){
    K++;A[K].r=r;A[K].nx=Fir[l];Fir[l]=K;
}
void dfs(int x,int f){
    vis[x]=1;
    if(x!=f)Add(f,x);
    for(int i=fir[x];i!=-1;i=a[i].nx)
    if(!vis[a[i].r]){
        dfs(a[i].r,f);
    }
}
bool check(int x){
    for(int i=Fir[x];i!=-1;i=A[i].nx)
    if(!sign[A[i].r]){
        sign[A[i].r]=1;
        if(link[A[i].r]==0){
            link[A[i].r]=x;
            return true;
        }
        if(check(link[A[i].r])){
            link[A[i].r]=x;
            return true;
        }
    }
    return false;
}
int hungery(){
    int ans=n;
    for(int i=1;i<=n;i++){
        memset(sign,0,sizeof(sign));
        if(check(i)) ans--;
    }
    return ans;
}
int main(){
    memset(fir,-1,sizeof(fir));
    memset(Fir,-1,sizeof(Fir));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&l,&r);
        add(l,r);
    }
    for(int i=1;i<=n;i++){
        memset(vis,0,sizeof(vis));
        dfs(i,i);
    }
    printf("%d",hungery());
}



猜你喜欢

转载自blog.csdn.net/bingoo0o0o/article/details/78701184