BZOJ 1143 祭祀river【二分图之偏序集的最大反链 】 附上网络流做法

传送门
//题意: 就是给定一个有向无环图, 选择尽量多的点使得其中任意的两个点都不能相互到达.
//思路:
在有向无环图中,有如下的一些定义和性质:
链:一条链是一些点的集合,链上任意两个点x, y,满足要么 x 能到达 y ,要么 y 能到达 x 。
反链:一条反链是一些点的集合,链上任意两个点x, y,满足 x 不能到达 y,且 y 也不能到达 x。
那么很显然这道题就是求最长反链长度了, 然后有以下定理:

一、有向无环图最小不相交路径覆盖
定义:用最少的不相交路径覆盖所有顶点。
定理:把原图中的每个点V拆成Vx和Vy,如果有一条有向边A->B,那么就加边Ax-By。这样就得到了一个二分图,最小路径覆盖=原图的节点数-新图最大匹配。
简单证明:一开始每个点都独立的为一条路径,总共有n条不相交路径。我们每次在二分图里加一条边就相当于把两条路径合成了一条路径,因为路径之间不能有公共点,所以加的边之间也不能有公共点,这就是匹配的定义。所以有:最小路径覆盖=原图的节点数-新图最大匹配。

二、有向无环图最小可相交路径覆盖
定义:用最小的可相交路径覆盖所有顶点。
算法:先用floyd求出原图的传递闭包,即如果a到b有路,那么就加边a->b。然后就转化成了最小不相交路径覆盖问题。

三、偏序集的最大反链
定义:偏序集中的最大独立集。
Dilworth定理:对于任意偏序集都有,最大独立集(最大反链)=最小链的划分(最小可相交路径覆盖).
通过Dilworth定理, 我们就可以把偏序集的最大独立集问题转化为最小可相交路径覆盖问题了.

然后知道这些了, 这道题就变成了裸题了…..

AC Code

const int maxn = 2e2+5;
int s[maxn][maxn];
int n,m;
void floyd()   //求传递闭包.
{
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                s[i][j]=min(s[i][j],s[i][k]+s[k][j]);
            }
        }
    }
}
bool vis[maxn];
int links[maxn];
vector<int>ve[maxn];
bool Find(int x)   //二分图匹配.
{
    vis[x] = 1;
    for(int i=0;i<ve[x].size();i++){
        int m = ve[x][i];
        if(vis[m]) continue;
        vis[m] = true;
        if(!links[m] || Find(links[m])){
            links[m] = x;   //有向图,注意标记的方向.
            return true;
        }
    }
    return false;
}
void solve() {
    scanf("%d%d" ,&n, &m);
    Fill(s,inf);
    for (int i = 1; i <= m ; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        s[u][v] = 1;
    }
    floyd();
    for (int i = 1 ; i <= n ; i++) {
        for (int j = 1 ; j <= n ; j++) {
            if (s[i][j] != inf) {
                ve[i].pb(j + n);   //拆点,直接加一个n即可.
                //千万不要忘了你建的是有向图.
            }
        }
    }
    int ans = 0; Fill(links,0);
    for (int i = 1 ; i <= n * 2; i++) {
        Fill(vis,0);
        if(!links[i] && Find(i))
            ans++;
    }
    cout << n - ans << endl;
}

因为我们会用到floyd求传递闭包, 这里是n^3的复杂度,所以对于点比较多的我们还是利用网络流的做法比较好, 其实也比上面这个写法要好写一点. 那就是对于原先图拆出来的点我们直接连一条inf边, 然后对于每一个点对应的拆点, 连一条拆点到原本点inf的边, 这样就可以利用一些点从而处理掉传递关系, 然后就是原本的点与源点直接连一条容量为1的边, 拆点与汇点连一条容量为1的边, 跑最大流即可.

网络流AC Code

const int maxn = 1e5+5;
int cnt ;
int n, m, s, t;
bool vis[maxn];
int d[maxn], cur[maxn];
struct Edge {
    int u, v;
    int cap, flow;
} e[maxn*6];    //因为是双向边  所以记得开二倍
vector<int> G[maxn];
void init() {
    cnt = 0;
    for (int i = 1 ; i <= n ; i ++) G[i].clear();
}


void add(int u, int v, int cap, int f) {
    e[cnt].u = u;
    e[cnt].cap = cap;
    e[cnt].flow = f;
    e[cnt].v = v;
}

void AddEdge(int u, int v, int cap) {
    add(u, v, cap, 0);
    G[u].push_back(cnt++);
    add(v, u, 0, 0);
    G[v].push_back(cnt++);
}

bool BFS() {
    Fill(vis, 0);
    queue<int> q; q.push(s);
    vis[s] = 1; d[s] = 0;
    while (!q.empty()) {
        int v = q.front(); q.pop();
        for (int i = 0; i < G[v].size(); i++) {
            Edge &te = e[G[v][i]];
            if (!vis[te.v] && te.cap > te.flow) { //只考虑残量网络的弧
                vis[te.v] = 1;
                d[te.v] = d[v] + 1;
                q.push(te.v);
            }
        }
    }
    return vis[t];
}

int dfs(int x, int a) {
    if (x == t || a == 0) return a;
    int flow = 0, f;
    for (int &i = cur[x]; i < G[x].size(); i++) { //从上次考虑的弧
        Edge &te = e[G[x][i]];
        if (d[x] + 1 == d[te.v] && (f = dfs(te.v, min(a, te.cap - te.flow))) > 0) {
            te.flow += f;
            e[G[x][i]^1].flow -= f;
            flow += f;
            a -= f;
            if (a == 0) break;
        }
    }
    return flow;
}

int Dinic() {
    int flow = 0;
    while (BFS()) {
        Fill(cur, 0);
        flow += dfs(s, inf);
    }
    return flow;
}

void solve() {
    scanf("%d%d", &n, &m);
    init(); s = 0, t = 2*n + 1;
    for (int i = 1 ; i <= m ; i ++) {
        int u, v;
        scanf("%d%d", &u, &v);
        AddEdge(u, v+n, inf);
    }
    for (int i = 1 ; i <= n ; i ++) {
        AddEdge(i+n, i, inf);
        AddEdge(s, i, 1);
        AddEdge(i+n, t, 1);
    }
    printf("%d\n", n - Dinic());
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值