[bzoj1497][NOI2006]最大获利——最大权闭合子图

7 篇文章 0 订阅
5 篇文章 0 订阅

题目大意:

给定n个点和m条边,建造每一个点有花费,建造每一条边有收益,但是一条边的建造必须要两个点都建造好才可以,求最大净收入。

思路:

把边和点都看成是点,边的的权值为正数,点的权值为负数,同时表示边的点向它的两个端点连都连一条有向边,要求选定一个集合满足这个集合不向外面有出边,求集合的最大权值和,就变成了最大权闭合子图问题,用网络流最小割解决。

/*=======================
 * Author : ylsoi
 * Problem : bzoj1497
 * Algorithm : Flow
 * Time : 2018.6.23
 * =====================*/
#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a;i<=b;++i)
#define inf INT_MAX
typedef long long ll;

using namespace std;

void File(){
    freopen("bzoj1497.in","r",stdin);
    freopen("bzoj1497.out","w",stdout);
}

const int maxn=5000+10;
const int maxm=50000+10;
int n,m,ss,tt,p[maxn],a[maxm],b[maxm],c[maxm],ans,tot;
int to[maxm<<3],last[maxm<<3],flow[maxm<<3],beg[maxn+maxm],cnte=1;

void add(int u,int v,int f){
    last[++cnte]=beg[u];
    beg[u]=cnte;
    to[cnte]=v;
    flow[cnte]=f;
}

void init(){
    scanf("%d%d",&n,&m);
    REP(i,1,n)scanf("%d",&p[i]);
    REP(i,1,m)scanf("%d%d%d",&a[i],&b[i],&c[i]),tot+=c[i];
    ss=0;tt=n+m+1;
    REP(i,1,m)add(ss,i,c[i]), add(i,ss,0);
    REP(i,1,n)add(m+i,tt,p[i]), add(tt,m+i,0);
    REP(i,1,m)add(i,m+a[i],inf), add(m+a[i],i,0), add(i,m+b[i],inf), add(m+b[i],i,0);
}

struct dinic{
    int cur[maxn+maxm],num[maxn+maxm];
    bool bfs(){
        memset(num,0,sizeof(num));
        queue<int>qu;
        qu.push(ss);
        num[ss]=1;
        while(qu.size()){
            int u=qu.front();
            qu.pop();
            if(u==tt)return true;
            for(int i=beg[u];i;i=last[i]){
                if(num[to[i]] || !flow[i])continue;
                qu.push(to[i]);
                num[to[i]]=num[u]+1;
            }
        }
        return false;
    }
    int dfs(int u,int gap){
        if(u==tt || !gap)return gap;
        int sum=0,f;
        for(int &i=cur[u];i;i=last[i]){
            if(num[to[i]]!=num[u]+1)continue;
            if((f=dfs(to[i],min(gap,flow[i])))){
                flow[i]-=f;
                flow[i^1]+=f;
                sum+=f;
                gap-=f;
            }
            if(!gap)break;
        }
        return sum;
    }
    void work(){
        while(bfs()){
            REP(i,ss,tt)cur[i]=beg[i];
            ans+=dfs(ss,inf);
        }
    }
}T;

int main(){
    File();
    init();
    T.work();
    printf("%d\n",tot-ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值