题解 P4662 【[BalticOI 2008]黑手党】
题目链接:Luogu 4662
Byteland 国警方收到了一条匿名举报,其中说当地黑帮老大正计划一次从港口到郊区仓库的运输。警方知道运输的时间并且知道运输需要用到国家的高速公路网。
高速公路网包含
从这个角度看,最简单的办法就是在每个收费站处都安排特警班。然而,控制第
- 所有从港口到仓库的交通必须至少经过集合中的一个收费站。
- 监控这些收费站的费用(即监控每一个收费站费用之和)最小。
- 你可以假设使用高速公路可以从港口到仓库。
你需要找到收费站的最小控制集。
数据范围:
Solution
我们需要将一些收费站安排特警班,使得源点和汇点不连通,于是这个问题就是最小割问题。
我们考虑如何将割点转化成割边。由于每个点只能被割一次,我们首先需要拆点:将点
由于源点和汇点也可以被割,所以源点转化为
最后我们证明一下正确性。此时,整张图的形态没有改变,每个点只能被割一次对应着每条边
对于控制集,我们从源点
时间复杂度:
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
const int N=4e2+5,M=1e5+5;
const int INF=1<<30;
int n,m,tot=1,lnk[N],ter[M],nxt[M],val[M],dep[N],cnr[N];
bool vis[N];
void add(int u,int v,int w) {
ter[++tot]=v,nxt[tot]=lnk[u],lnk[u]=tot,val[tot]=w;
}
void addedge(int u,int v,int w) {
add(u,v,w),add(v,u,0);
}
int bfs(int s,int t) {
memset(dep,0,sizeof(dep));
memcpy(cnr,lnk,sizeof(lnk));
std::queue<int> q;
q.push(s),dep[s]=1;
while(!q.empty()) {
int u=q.front(); q.pop();
for(int i=lnk[u];i;i=nxt[i]) {
int v=ter[i];
if(val[i]&&!dep[v]) q.push(v),dep[v]=dep[u]+1;
}
}
return dep[t];
}
int dfs(int u,int t,int flow) {
if(u==t) return flow;
int ans=0;
for(int i=cnr[u];i&&ans<flow;i=nxt[i]) {
cnr[u]=i;
int v=ter[i];
if(val[i]&&dep[v]==dep[u]+1) {
int x=dfs(v,t,std::min(val[i],flow-ans));
if(x) val[i]-=x,val[i^1]+=x,ans+=x;
}
}
if(ans<flow) dep[u]=-1;
return ans;
}
void dinic(int s,int t) {
while(bfs(s,t)) while(dfs(s,t,INF));
}
void dfs(int u) {
vis[u]=1;
for(int i=lnk[u];i;i=nxt[i]) if(val[i]&&!vis[ter[i]]) dfs(ter[i]);
}
int main() {
int s,t;
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=n;++i) {
int x;
scanf("%d",&x);
addedge(i,i+n,x);
}
while(m--) {
int u,v;
scanf("%d%d",&u,&v);
addedge(u+n,v,INF),addedge(v+n,u,INF);
}
dinic(s,t+n);
dfs(s);
std::vector<int> ans;
for(int i=2;i<=tot;i+=2) {
int u=ter[i^1],v=ter[i];
if(vis[u]&&!vis[v]) ans.push_back(u);
}
std::sort(ans.begin(),ans.end());
for(int sz=(int)ans.size()-1,i=0;i<=sz;++i) {
printf("%d%c",ans[i]," \n"[i==sz]);
}
return 0;
}