『Loj6197』 法克

简述题意

给一张 \(n\) 个点 \(m\) 条边的 DAG ,问能选择的最大的点集 \(S\) 使得 \(\forall u,v\in S\)\(u\) 无法到达 \(v\)\(v\) 无法到达 \(u\)

\(n\leq 10^5,m\leq 2n\)

Solution

首先先给出结论:\(ans=\) 最小可交路径覆盖。

证明

  1. 最小可交路径覆盖是答案下界
    对于最小可交路径覆盖中的任意一条路径 \(L\) ,上面一定可以找到一个点 \(u\) 满足 \(u\) 只被 \(L\) 覆盖过,否则 \(L\) 这条路径可以删去,与最小相矛盾。
    那么对于每一条路径 \(L\),我们可以选择那个只被 \(L\) 覆盖的那个点 \(u\) ,不难发现这样子选出来的集合 \(S\) 一定合法。

  2. 最小可交路径覆盖是答案上界
    根据鸽巢原理,如果答案比最小可交路径覆盖大,那么必然有一条链 \(L\) 同时存在两个点,那么此时的点集 \(S\) 不合法。

综述答案就是最小可交路径覆盖。

关于传统的最小不可交路径覆盖,做法是把一个点拆成入和出之后跑二分图最大匹配,证明这里略去。

而可交路径的解决方法则是先跑一次传递闭包,然后再仿照上面的做法,这样子复杂度是 \(O(nm)\) 的,无法接受。

实际上还有另外一种更简单而且复杂度更优秀的做法,只需要从 \(n+i\)\(i\) 连一条 inf 边即可。

感性理解一下,传递闭包实际上就是 \(u\) 能向所有 \(u\) 能到达的点连一条边,那么在 \(n+u\)\(u\) 连边之后就可以实现 \(u\rightarrow n+v \rightarrow v\) ,那么就等价于将 \(u\) 向所有 \(v\) 连边了的点连边,那么和做一次传递闭包是等价的。

Code

Code
#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
#define ll long long 
#define ri int
#define pii pair<int,int>
const ll mod=998244353;
ll add(ll x,ll y){return (x+=y)<mod?x:x-mod;}
ll dec(ll x,ll y){return (x-=y)<0?x+mod:x;}
ll ksm(ll d,ll t,ll res=1){for(;t;t>>=1,d=d*d%mod) if(t&1) res=res*d%mod;return res;}
int n,m;
const int MAXN=2e5+7,inf=1e9;
namespace Flow{
    int hed[MAXN],nxt[MAXN<<3],cnt,rad[MAXN],dep[MAXN],s,t;
    struct edge{
        int to,c;
    }e[MAXN<<3];
    void init(){
        s=0,t=2*n+1;
        memset(hed,-1,sizeof(hed));
        memset(nxt,-1,sizeof(nxt));
        cnt=-1;
    }
    void link(int u,int v,int c){
        e[++cnt]=(edge){v,c},nxt[cnt]=hed[u],hed[u]=cnt;
        e[++cnt]=(edge){u,0},nxt[cnt]=hed[v],hed[v]=cnt;
    }
    bool bfs(){
        for(ri i=0;i<=t;++i) dep[i]=0,rad[i]=hed[i];
        queue<int> q;q.push(s),dep[s]=1;
        while(!q.empty()){
            int u=q.front();q.pop();
            for(ri x=hed[u];~x;x=nxt[x]){
                int v=e[x].to;if(!e[x].c||dep[v]) continue;
                dep[v]=dep[u]+1,q.push(v);
            }
        }
        return dep[t];
    }
    int Flow(int u,int flow=inf){
        if(u==t||!flow) return flow;
        int tot=0;
        for(ri x=rad[u];~x;x=rad[u]){
            rad[u]=nxt[x];
            int v=e[x].to;if(dep[v]!=dep[u]+1||!e[x].c) continue;
            int res=Flow(v,min(flow,e[x].c));
            e[x].c-=res,e[x^1].c+=res,flow-=res,tot+=res;
            if(!flow) break;
        }
        if(!tot) dep[u]=0;
        return tot;
    }
    int solve(){
        int res=0;
        while(bfs()){
            res+=Flow(s);
        }
        return res;
    }
    void check(int u){
        for(ri x=hed[u];~x;x=nxt[x]) printf("%d %d %d\n",u,e[x].to,e[x].c);
    }
}
int main(){
    scanf("%d%d",&n,&m);Flow::init();
    for(ri i=1;i<=n;++i) Flow::link(Flow::s,i,1),Flow::link(n+i,Flow::t,1),Flow::link(n+i,i,inf);
    for(ri i=1;i<=m;++i){
        int u,v;scanf("%d%d",&u,&v);
        Flow::link(u,n+v,inf);
    }
    // Flow::check(3);
    printf("%d\n",n-Flow::solve());
}
posted @ 2022-01-09 20:01  krimson  阅读(127)  评论(0编辑  收藏  举报