测试地址:最大获利
做法:首先介绍一下闭合子图和最大权闭合子图的概念:在一个有向图中选择一个点集V,若V满足对于任意点X∈V,若有向边(X,Y)存在,则Y∈V,即V中所有点的出边终点也属于V,则称V为一个闭合子图,最大权闭合子图就是在原图有点权的情况下,在所有闭合子图中点权和最大的一个闭合子图。因为点权可能有正有负,所以不能简单粗暴的求解,但是我们可以将其转化为求最小割的问题,进而转化为求最大流的问题,转化方法可以看这里。
那么我们就可以很容易的看出这个题目就是一个最大权闭合子图的模型,要选择用户群,也必须选择该用户群所需的中转站,则从用户群向其所需的中转站连边,然后按照以上模型建图求解即可。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#define inf 1000000000
using namespace std;
int n,m,sum=0,first[60010],tot=1;
int level[60010];
struct edge {int v,f,next;} e[1000010];
void insert(int a,int b,int f)
{
e[++tot].v=b,e[tot].f=f,e[tot].next=first[a],first[a]=tot;
e[++tot].v=a,e[tot].f=0,e[tot].next=first[b],first[b]=tot;
}
bool makelevel()
{
queue<int> q;
memset(level,0,sizeof(level));
level[0]=1;
q.push(0);
while(!q.empty())
{
int v=q.front();q.pop();
for(int i=first[v];i;i=e[i].next)
if (!level[e[i].v]&&e[i].f)
{
level[e[i].v]=level[v]+1;
q.push(e[i].v);
}
}
return level[n+m+1];
}
int dfs(int v,int maxf)
{
int ret=0,f;
if (v==n+m+1) return maxf;
for(int i=first[v];i;i=e[i].next)
if (level[e[i].v]==level[v]+1&&e[i].f)
{
f=dfs(e[i].v,min(maxf-ret,e[i].f)); //一定要注意减ret!!!太容易错了!
e[i].f-=f;
e[i^1].f+=f;
ret+=f;
if (ret==maxf) return ret;
}
return ret;
}
int dinic()
{
int ans=0;
while(makelevel())
{
ans+=dfs(0,inf);
}
return ans;
}
int main()
{
memset(first,0,sizeof(first));
scanf("%d%d",&n,&m);
for(int i=1,a;i<=n;i++)
{
scanf("%d",&a);
insert(i,n+m+1,a);
}
for(int i=1,a,b,c;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
sum+=c;
insert(0,n+i,c);
insert(n+i,a,inf);
insert(n+i,b,inf);
}
printf("%d",sum-dinic());
return 0;
}