有向图闭合图:
闭合图内任意点的任意后继都在闭合图中。
要创造一个闭合子图,如果选择了一个点,就必须把这个点的所有后继都选上。
可以用于表示事物间依赖关系:
如果一个事件要发生,它需要的所有前提(图中为后继)也必须都要发生。
最大权闭合子图:
点权之和最大的闭合子图
建图:
源点->原图正点权事件,容量为点权
原图负权点事件->汇点,容量为点权绝对值
事件边的边权修改为inf即(前驱->后继,容量inf)
(据说点权为0的可以忽略,不影响结果)
闭合图的权为正权点总和减去该图的割
当割最小时,闭合图权最大
如果源点与点x有边,表示点x存在子图中;
如果点x与汇点有边,表示点x不存在于子图中。
P4174 最大获利
思路:
建立中转站看作一个点
为用户提供服务也看作一个点
显然建立中转站点权为负(因为要成本)
为用户提供服务点权为正(因为获益)
然后考虑两者之间的关系:
选择了某个用户,如果该用户要从a->b收益c
则说明选择了这个用户必须选择建立中转站a和中转站b
显然建立中转站a、b为其后继
建图:
事件点->中转站a , 事件点->中转站b ,容量为inf
源点->事件点 ,容量为获益c
中转站->汇点,容量为成本p
求最小割,最后的答案为正权之和-最小割
code:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int maxm=2e5+5;
const int inf=1e9;
int head[maxm],nt[maxm<<1],to[maxm<<1],w[maxm<<1],cnt;
int d[maxm];
int n,m;
int s,t;
int maxflow;
void init(){
memset(head,-1,sizeof head);
cnt=1;
}
void add(int x,int y,int z){
cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
cnt++;nt[cnt]=head[y];head[y]=cnt;to[cnt]=x;w[cnt]=0;
}
bool bfs(){
queue<int>q;
q.push(s);
memset(d,0,sizeof d);
d[s]=1;
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i!=-1;i=nt[i]){
int v=to[i];
if(w[i]&&!d[v]){
d[v]=d[x]+1;
q.push(v);
if(v==t)return 1;
}
}
}
return 0;
}
int dfs(int x,int flow){
if(x==t)return flow;
int res=flow;
for(int i=head[x];i!=-1;i=nt[i]){
int v=to[i];
if(w[i]&&d[v]==d[x]+1){
int k=dfs(v,min(res,w[i]));
w[i]-=k;
w[i^1]+=k;
res-=k;
if(!k)d[v]=-1;
if(!res)break;
}
}
return flow-res;
}
void dinic(){
maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
}
int main(){
init();
scanf("%d%d",&n,&m);
s=0,t=n+m+1;
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);
add(i,t,x);
}
int sum=0;
for(int i=1;i<=m;i++){//事件变为点1+n~m+n
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
sum+=c;
add(s,i+n,c);
add(i+n,a,inf);
add(i+n,b,inf);
}
dinic();
printf("%d\n",sum-maxflow);
return 0;
}
poj2987 Firing
题意:
公司要解雇一些人,某些人解雇可以获益,某些人解雇会亏钱,解雇一个人则这个人手下的所有人都必须解雇,也就是说一个人的上司被解雇了这个人也会被解雇,现在给n个人解雇的收益或损失以及这n个人的从属关系,求得到最大收益需要解雇的人数和最大收益。
思路:
显然要求得是闭合子图的最大权,收益很简单直接求就行了,但是还要求输出解雇的人数,
根据和源点相连的点都是要选择的,所以可以在求完最小割之后的残余网络上从源点开始dfs,统计搜到的点就行了,注意要mark搜到的点防止再搜,否则可能死循环
ps:
题目数据比较大需要longlong
code:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
#define int long long
const int maxm=2e5+5;
const int inf=1e18;
int head[maxm],nt[maxm<<1],to[maxm<<1],w[maxm<<1],cnt;
int mark[maxm];
int d[maxm];
int n,m;
int s,t;
int maxflow;
int ans;
void init(){
memset(head,-1,sizeof head);
cnt=1;
}
void add(int x,int y,int z){
cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
cnt++;nt[cnt]=head[y];head[y]=cnt;to[cnt]=x;w[cnt]=0;
}
bool bfs(){
queue<int>q;
q.push(s);
memset(d,0,sizeof d);
d[s]=1;
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i!=-1;i=nt[i]){
int v=to[i];
if(w[i]&&!d[v]){
d[v]=d[x]+1;
q.push(v);
if(v==t)return 1;
}
}
}
return 0;
}
int dfs(int x,int flow){
if(x==t)return flow;
int res=flow;
for(int i=head[x];i!=-1;i=nt[i]){
int v=to[i];
if(w[i]&&d[v]==d[x]+1){
int k=dfs(v,min(res,w[i]));
w[i]-=k;
w[i^1]+=k;
res-=k;
if(!k)d[v]=-1;
if(!res)break;
}
}
return flow-res;
}
void dinic(){
maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
}
void getans(int x){
mark[x]=1;
for(int i=head[x];i!=-1;i=nt[i]){
int v=to[i];
if(w[i]&&!mark[v]){
ans++;
getans(v);
}
}
}
signed main(){
init();
scanf("%lld%lld",&n,&m);
s=0,t=n+1;
int sum=0;
for(int i=1;i<=n;i++){
int x;
scanf("%lld",&x);
if(x>0){
sum+=x;
add(s,i,x);
}else{
add(i,t,-x);
}
}
for(int i=1;i<=m;i++){
int a,b;
scanf("%lld%lld",&a,&b);
add(a,b,inf);
}
dinic();
getans(s);
printf("%lld %lld\n",ans,sum-maxflow);
return 0;
}
CodeForces 1082 G.Petya and Graph
题意:
n个点m条边的图,点有点权,边有边权
要求选一出一个子图,使得边权和减去点权和最大
如果选择了某一条边,则边的两个端点都必须选
思路:
最大权闭合子图裸题
如果选择了某一条边,则边的两个端点都必须选,可以看出事件的先后关系。
显然边是正权事件,点是负权事件
建图:
源点->边,容量为边权
边->端点1,边->端点2,容量为inf
点->汇点,容量为点权
答案为边权和减去最小割(即最大流)
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int inf=1e17;
const int maxm=3e3+5;
int head[maxm],nt[maxm<<2],to[maxm<<2],w[maxm<<2],cnt;
int d[maxm];
int p[maxm];
int n,m;
int s,t;
int maxflow;
void init(){
memset(head,-1,sizeof head);
cnt=1;
}
void add(int x,int y,int z){
cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
cnt++;nt[cnt]=head[y];head[y]=cnt;to[cnt]=x;w[cnt]=0;
}
bool bfs(){//分层图
queue<int>q;
q.push(s);
memset(d,0,sizeof d);
d[s]=1;
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i!=-1;i=nt[i]){
int v=to[i];
if(w[i]&&!d[v]){
d[v]=d[x]+1;
q.push(v);
if(v==t)return 1;
}
}
}
return 0;
}
int dfs(int x,int flow){//求流
if(x==t)return flow;
int res=flow;
for(int i=head[x];i!=-1;i=nt[i]){
int v=to[i];
if(w[i]&&d[v]==d[x]+1){
int k=dfs(v,min(res,w[i]));
w[i]-=k;
w[i^1]+=k;
res-=k;
if(!k)d[v]=-1;
if(!res)break;
}
}
return flow-res;
}
void dinic(){
maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
}
signed main(){
init();
cin>>n>>m;
s=0,t=n+m+1;
for(int i=1;i<=n;i++){
cin>>p[i];
add(i,t,p[i]);//负权事件连接汇点
}
int sum=0;
for(int i=1;i<=m;i++){//边(n+1)-(n+m)
int a,b,c;
cin>>a>>b>>c;
sum+=c;//累加至正权和
add(s,n+i,c);//源点到边,源点连正权事件
add(n+i,a,inf);//事件边连接两个端点
add(n+i,b,inf);//事件边连接两个端点
}
dinic();
cout<<sum-maxflow<<endl;
return 0;
}