POJ 2987 - Firing (最大权闭合子图)
题意:求一个图的最大权闭合子图
最小割模型的应用,源点向正权点建容量为该权值的弧;负权点向汇点建容量为该权值绝对值的弧;有偏序关系的点对之间建容量为正无穷的弧.
跑出最大流,根据最大流最小割定理:最大流的值即最小割.所求最大闭合子图的值为|正权点权值之和| - |最小割|.
此外,还要求删去人的数目.那么从源点出发,且不是最小割的边,就是被选择的边.
所以从源点dfs,若遇到容量不为0的边则结果++.最后记得减去源点
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=6010;//点数的最大值
const int MAXM=100010;//边数的最大值
#define captype long long
typedef long long LL;
const LL INF = (1LL)<<60;
struct SAP_MaxFlow{
struct EDGE{
int to,next;
captype cap;
}edg[MAXM];
int eid,head[MAXN];
int gap[MAXN];
int dis[MAXN];
int cur[MAXN];
int pre[MAXN];
void init(){
eid=0;
memset(head,-1,sizeof(head));
}
void AddEdge(int u,int v,captype c,captype rc=0){
edg[eid].to=v; edg[eid].next=head[u];
edg[eid].cap=c; head[u]=eid++;
edg[eid].to=u; edg[eid].next=head[v];
edg[eid].cap=rc; head[v]=eid++;
}
captype maxFlow_sap(int sNode,int eNode, int n){//n是包括源点和汇点的总点个数,这个一定要注意
memset(gap,0,sizeof(gap));
memset(dis,0,sizeof(dis));
memcpy(cur,head,sizeof(head));
pre[sNode] = -1;
gap[0]=n;
captype ans=0;
int u=sNode;
while(dis[sNode]<n){
if(u==eNode){
captype Min=INF ;
int inser;
for(int i=pre[u]; i!=-1; i=pre[edg[i^1].to])
if(Min>edg[i].cap){
Min=edg[i].cap;
inser=i;
}
for(int i=pre[u]; i!=-1; i=pre[edg[i^1].to]){
edg[i].cap-=Min;
edg[i^1].cap+=Min;
}
ans+=Min;
u=edg[inser^1].to;
continue;
}
bool flag = false;
int v;
for(int i=cur[u]; i!=-1; i=edg[i].next){
v=edg[i].to;
if(edg[i].cap>0 && dis[u]==dis[v]+1){
flag=true;
cur[u]=pre[v]=i;
break;
}
}
if(flag){
u=v;
continue;
}
int Mind= n;
for(int i=head[u]; i!=-1; i=edg[i].next)
if(edg[i].cap>0 && Mind>dis[edg[i].to]){
Mind=dis[edg[i].to];
cur[u]=i;
}
gap[dis[u]]--;
if(gap[dis[u]]==0) return ans;
dis[u]=Mind+1;
gap[dis[u]]++;
if(u!=sNode) u=edg[pre[u]^1].to; //退一条边
}
return ans;
}
}F;
bool vis[MAXN];
int tot = 0;
void dfs(int u)
{
vis[u] = true;
tot++;
for(int i=F.head[u];~i;i=F.edg[i].next){
int v = F.edg[i].to;
if(!vis[v] && F.edg[i].cap>0){
dfs(v);
}
}
}
LL val[MAXN];
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
int n,m;
while(scanf("%d %d",&n,&m)==2){
F.init();
LL sum = 0;
tot=0;
int s = 0,t = n+1,u,v;
for(int i=1;i<=n;++i){
scanf("%lld",&val[i]);
if(val[i]>0){
sum += val[i]; //统计正权边之和
F.AddEdge(s,i,val[i]);
}
else{
F.AddEdge(i,t,-val[i]);
}
}
for(int i=1;i<=m;++i){
scanf("%d %d",&u,&v);
F.AddEdge(u,v,INF);
}
LL f = F.maxFlow_sap(s,t,t+1);
LL res = sum - f; //最大权值为 正权和-最小割
memset(vis,0,sizeof(vis));
dfs(s); //计算割的边数
printf("%d %lld\n",tot-1,res); //多算了一个
}
return 0;
}
为了更好的明天