『Loj6197』 法克
简述题意
给一张 \(n\) 个点 \(m\) 条边的 DAG ,问能选择的最大的点集 \(S\) 使得 \(\forall u,v\in S\) ,\(u\) 无法到达 \(v\) 且 \(v\) 无法到达 \(u\) 。
\(n\leq 10^5,m\leq 2n\)
Solution
首先先给出结论:\(ans=\) 最小可交路径覆盖。
证明
-
最小可交路径覆盖是答案下界
对于最小可交路径覆盖中的任意一条路径 \(L\) ,上面一定可以找到一个点 \(u\) 满足 \(u\) 只被 \(L\) 覆盖过,否则 \(L\) 这条路径可以删去,与最小相矛盾。
那么对于每一条路径 \(L\),我们可以选择那个只被 \(L\) 覆盖的那个点 \(u\) ,不难发现这样子选出来的集合 \(S\) 一定合法。 -
最小可交路径覆盖是答案上界
根据鸽巢原理,如果答案比最小可交路径覆盖大,那么必然有一条链 \(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());
}