BZOJ 1497: [NOI2006]最大获利

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1497


/**
转化成最大权闭合子图,如果要满足用户群C,就要建立A,B站点
把用户群看成一个个节点,则可以把他们划分成两个集合,第一个
集合是得到满足的集合,另一个是未得到满足的集合,满足的设为
S集合,未满足设未T集合,对于一个用户群,如果它属于T集合,则
会损失一些收益,对于每一个点,如果有收益,其点权值未正值,
如果需要建站,则点权值是负值,按照胡伯涛论文中所述,对于点
权是正值的点,我们与超级源点S建立关系,其值就是点的权值,如果
点权是赋值的点,与超级汇点建立联系,其权值是点权的绝对值,
然后点与点之间有联系的话,建立边权值未INF。
对于这个题目,从源点S与每一个用户群建立一条边,权值未满足该
用户群后所能获得的收益,每一个基站与超级汇点建立一条边,其
权值为建立该基站的费用,又由于满足该用户群,需要建立A,B基站,
则添加该用户群节点到A的边其权值为INF,添加该用户群节点到B的
边其权值为INF。纯收入 = 总利润-成本=(所有正的点权值之和)-最小割
最终求得最小割,从S出发进行dfs,所能遍历到得点都是属于S的点,
用户群属于S则,则满足该用户群的基站也必然属于S割,因为用户
群到这两个点的边都是无穷大,没有流可以阻断权重为无穷大的边。
只要用户群属于S割,则可以获取其收益。所以我们就先假设能获取
所有的收益最后减去用户群属于T割造成的损失,就是纯利润。
*/

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <queue>

using namespace std;

const int maxn = 60000;
const int INF =  0x7fffffff;
struct Edge {
    int to,cap,nex;
}edge[maxn<<3];
int cnt,level[maxn],head[maxn],cur[maxn],S,T,N,M;
void addEdge(int u,int v,int w) {
    edge[cnt].to = v;
    edge[cnt].cap = w;
    edge[cnt].nex = head[u];
    head[u] = cnt++;

    edge[cnt].to = u;
    edge[cnt].cap = 0;
    edge[cnt].nex = head[v];
    head[v] = cnt++;
}
bool bfs() {
    memset(level,-1,sizeof(level));
    queue<int>qu;
    level[S] = 0;
    qu.push(S);
    while(!qu.empty()) {
        int u = qu.front();
        qu.pop();
        for(int i = head[u]; i != -1; i = edge[i].nex) {
            int v = edge[i].to;
            if(edge[i].cap>0 && level[v]==-1) {
                level[v] = level[u] + 1;
                qu.push(v);
            }
        }
    }
    if(level[T] == -1)
        return false;
    else
        return true;
}
int dfs(int u,int flow) {
    if(u == T) return flow;
    int tmp,sum = 0;
    for(int &i = cur[u]; i != -1; i = edge[i].nex) {
        int v = edge[i].to;
        if(edge[i].cap>0 && level[v]==level[u]+1) {
            tmp = dfs(v,min(flow-sum,edge[i].cap));
            edge[i].cap -= tmp;
            edge[i^1].cap += tmp;
            sum += tmp;   ///sum是流出的流量
            if(sum == flow) return flow;  ///节点流量平衡,返回
        }
    }
    if(sum == 0) level[u] = -1;
    return sum;
}
int dinic() {
    int maxFlow = 0;
    while(bfs()) {
        for(int i = S; i <= T; i++)
            cur[i] = head[i];
        maxFlow += dfs(S,INF);
    }
    return maxFlow;
}
int main() {
    while(~scanf("%d%d",&N,&M)) {
        int cost;
        cnt = 0;
        memset(head,-1,sizeof(head));
        S = 0;
        T = N+M+1;
        for(int i = 1; i <= N; i++) {
            scanf("%d",&cost);
            addEdge(i,T,cost);
        }
        int profit = 0;
        int A,B,C;
        for(int i = 1; i <= M; i++) {
            scanf("%d%d%d",&A,&B,&C);
            addEdge(S,N+i,C);
            addEdge(N+i,A,INF);
            addEdge(N+i,B,INF);
            profit += C;
        }
        int ans = profit - dinic();
        printf("%d\n",ans);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值