大意:
给定一张图,求其最小割点集的大小
思路:
蒟蒻如我,看到题目直接嗷嗷大叫:“卧槽是裸的最小割边!这题我会做!”
兴冲冲敲完dinic莽一发
80分。。。
做题从不带眼睛。。。(思路都错了居然也还有80分。。。)
那这个题是要求最小割点,所以我们不妨想想看怎么把它转化为普通的最大流问题。
一个很精妙的思路是拆点。
把一个点A拆成A和A',两者有一条单向边AA',权值为1,为了构造网络流我们就会让其反向边权值为0.那么当我们要拆点是,实质上就是把这条权值为1的边去了,也就是流量减了1.现在要求点的最小割,自然也就是求边的最小割了,也就是最大流。那么这个多出来的点不妨就设为i+n。
for(int i=1;i<=n;++i)
{
add(i,i+n,1);
add(i+n,i,0);//拆点小trick
}
那么模型转化完了,还有一个要考虑的是其余的普通边的问题,它们要怎么连?
需要注意的是,我们求的最小割,是针对这些把点分开的边,而不是普通的边,所以这些普通双向边的加入不应对我们的流量造成影响,换句话说,我们应该把它们的流量设为inf。而且它们跟那些边权为1的边应该是串联的关系,也就是y应该跟x+n连,x应该跟y+n连,不然如果是直接x跟y连,两个点的联通就可以做到跟1没关系了,那求出来的最大流自然也会变成inf。可以看图理解一下。
(反向边我就不画了)
代码就应该是
for(int i=1;i<=m;++i)
{
a=read();b=read();
add(a+n,b,inf);
add(b,a+n,0);
add(b+n,a,inf);
add(a,b+n,0);
}
这些准备工作都做完后,那就是快乐的板子时间了!
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=10100;
#define endl '\n'
const ll inf=1<<30;
struct ty{
ll t,l,next;
}edge[N<<2];
ll cnt=1;//后面要让第i条边^1后能得到它的反向边(与它相邻)
ll head[N];
inline ll read(){
int x=0;
char c=getchar();
while(c>'9'||c<'0')c=getchar();
while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
return x;
}
void add(ll a,ll b,ll c)
{
edge[++cnt].t=b;
edge[cnt].l=c;
edge[cnt].next=head[a];
head[a]=cnt;
}
ll inque[N];
ll dep[N];//用于分层
ll cur[N];//记录弧
ll vis;//这次是否能到达终点
ll ans=0;
ll n,m,s,t,x;
ll a,b,c;
bool bfs()//用于分层
{
// memset(inque,0,sizeof inque);
// memset(dep,0x3f,sizeof dep);
for(int i=0;i<=n*2;++i)
{
cur[i]=head[i];
dep[i]=0x3f3f3f3f3f3f3f3f;
inque[i]=0;
}
dep[s]=0;//起点
queue<ll> q;
q.push(s);
while(!q.empty())
{
ll ty=q.front();
q.pop();
inque[ty]=0;//
for(int i=head[ty];i!=-1;i=edge[i].next)
{
ll y=edge[i].t;
if(dep[y]<=dep[ty]+1) continue;//无需更新
if(edge[i].l==0) continue;
dep[y]=dep[ty]+1;
if(inque[y]) continue;//看是否在队里
inque[y]++;
q.push(y);
}
}
if(dep[t]!=0x3f3f3f3f3f3f3f3f) return 1;
return 0;
}
ll dfs(ll u,ll flow)//节点,当前最小流量
{
ll rl=0;
if(u==t)
{
vis=1;
ans+=flow;
return flow;
}
ll used=0;//该点用过的流量
for(int i=cur[u]/*是cur!这里不是head(当前弧优化)*/;i!=-1;i=edge[i].next)
{
cur[u]=i;//修改当前弧
ll y=edge[i].t;
if(dep[y]==dep[u]+1&&edge[i].l)
{
rl=dfs(y,min(flow-used,edge[i].l));//减去后的费用拿去比较
if(rl==0) continue;
used+=rl;
edge[i].l-=rl;
edge[i^1].l+=rl;
if(used==flow) break;//满了
//return rl;
}
}
return used;
}
ll dic()
{
while(bfs())
{
vis=1;
while(vis)
{
vis=0;
dfs(s,inf);
}
}
return ans;
}
int main()
{
memset(head,-1,sizeof head);
n=read();m=read();t=read();s=read();
s+=n;//拆点的话就不要忘了更新终点!!!
for(int i=1;i<=n;++i)
{
add(i,i+n,1);
add(i+n,i,0);//拆点小trick
}
for(int i=1;i<=m;++i)
{
a=read();b=read();
add(a+n,b,inf);
add(b,a+n,0);
add(b+n,a,inf);
add(a,b+n,0);
}
ll k=dic();
cout<<k<<endl;
return 0;
}