最大权闭合图 BZOJ 1497 最大获利 (最小割)

BZOJ 1497 最大获利

Time Limit: 5 Sec Memory Limit: 64 MB

Description
新的技术正冲击着手机通讯市场,对于各大运营商来说,这既是机遇,更是挑战。THU集团旗下的CS&T通讯公司在新一代通讯技术血战的前夜,需要做太多的准备工作,仅就站址选择一项,就需要完成前期市场研究、站址勘测、最优化等项目。在前期市场调查和站址勘测之后,公司得到了一共N个可以作为通讯信号中转站的地址,而由于这些地址的地理位置差异,在不同的地方建造通讯中转站需要投入的成本也是不一样的,所幸在前期调查之后这些都是已知数据:建立第i个通讯中转站需要的成本为Pi(1≤i≤N)。另外公司调查得出了所有期望中的用户群,一共M个。关于第i个用户群的信息概括为Ai, Bi和Ci:这些用户会使用中转站Ai和中转站Bi进行通讯,公司可以获益Ci。(1≤i≤M, 1≤Ai, Bi≤N) THU集团的CS&T公司可以有选择的建立一些中转站(投入成本),为一些用户提供服务并获得收益(获益之和)。那么如何选择最终建立的中转站才能让公司的净获利最大呢?(净获利 = 获益之和 - 投入成本之和)

Input
输入文件中第一行有两个正整数N和M 。第二行中有N个整数描述每一个通讯中转站的建立成本,依次为P1, P2, …, PN 。以下M行,第(i + 2)行的三个数Ai, Bi和Ci描述第i个用户群的信息。所有变量的含义可以参见题目描述。

Output
你的程序只要向输出文件输出一个整数,表示公司可以得到的最大净获利。

Sample Input
5 5

1 2 3 4 5

1 2 3

2 3 4

1 3 3

1 4 2

4 5 3

Sample Output
4

HINT
【样例说明】选择建立1、2、3号中转站,则需要投入成本6,获利为10,因此得到最大收益4。【评分方法】本题没有部分分,你的程序的输出只有和我们的答案完全一致才能获得满分,否则不得分。【数据规模和约定】 80%的数据中:N≤200,M≤1 000。 100%的数据中:N≤5 000,M≤50 000,0≤Ci≤100,0≤Pi≤100。

思路:
把若干点分为两个集合,不同的选择方法可以获得不同的利润,这种非此即彼并且要求最优解的问题,可以考虑考虑最小割。因为它会把所有点分为两个集合,我们可以先不管限定条件,直接取最值,然后用最小割分为两个集合,让它满足条件。在此过程中,会损失的利益就是我们要割掉的边。
这道题中涉及到一个常用的处理操作,针对这一类有正负点权的题目,往往采用下放点权为边流量。因为网络流中不可能添加流量为负的边,我们可以做到的就只是割边计算损失,所以我们还是先考虑取到最优(获得了所有用户),那么建立站就是割掉一条流量为代价(正值)的边,失去一个用户群就是割掉一条流量为收益的边。(这样就成功地将加负值转化成了最大获益减正值)
所以先将所有收益加起来,再减去最小代价,即为最终答案。
把用户群和中转站看成n+m个点。
从源点s到所有用户节点i连边(s,i,c[i]),表示如果用户i不能满足,就会付出c[i]的代价。
从所有中转站节点i到汇点t连边(i,t,p[i]),表示如果要建立中转站i,就要付出p[i]的代价。
然后考虑用户对中转站的要求,两个中转站中只要有一个没有,这个用户就不能满足,即只要有一个中转站属于t割,那该用户也属于t割。只要连边(i,a[i],inf)和(i,b[i],inf),因为长度为inf的边是一定不会成为割的。我们要么就割掉两个中转站到T的边,说明建了这两个中转站获得了这个用户,要么就割掉这个用户到S的边说明我们可以不建这两个站,放弃掉这个用户群。
在一个图中,我们选取一些点构成集合,记为V,且集合中的出边(即集合中的点的向外连出的弧),所指向的终点(弧头)也在V中,则我们称V为闭合图。最大权闭合图即在所有闭合图中,集合中点的权值之和最大的V,我们称V为最大权闭合图。
通过这道题,我们不难得出:
最大权闭合图的的权 = 原图中权值为正的点的和(所有用户的收益之和) - 最小割(最大流)

#include<iostream>  
#include<cstdio> 
#include<cstring>
#include<algorithm>
#define LL long long  
#define N 100000
#define INF 1000000000  
using namespace std;

struct Edge{
    int next, to, w;  
}ed[N*4];

int head[N],cur[N],dis[N],q[N],exi[N];  
int n,m,S,T,idc=1,ans,sum;

void adde(int u, int v, int w){  
    ed[++idc].to = v;
    ed[idc].w = w;
    ed[idc].next = head[u];
    head[u]=idc;  
}  

bool bfs(){
    memset(dis,-1,sizeof(dis));
    int h = 0, t = 1;
    dis[S] = 0; q[1] = S; exi[S] = 1; 
    while( h < t ){
        int u = q[++h];
        exi[u] = 0; 
        for(int k=head[u]; k; k=ed[k].next){
            int v = ed[k].to;
            if(ed[k].w && dis[v] == -1){  
                dis[v] = dis[u] + 1;  
                if( !exi[v] ) q[++t] = v;
                if(v == T) break;  
            }  
        }
    }  
    return dis[T] != -1;  
}  

int dfs(int u, int flow){  
    int low, totflow = 0;  
    if(u == T || !flow) return flow;  
    for(int k=cur[u]; k; cur[u]=k=ed[k].next){  
        int v = ed[k].to;  
        if(ed[k].w && dis[v] == dis[u] + 1){  
            low = dfs(v, min(flow, ed[k].w));  
            ed[k].w -= low; ed[k^1].w += low;
            totflow += low; flow -= low;
            if(flow == 0) return totflow;
        }  
    }  
    if(!totflow) dis[u] = -1;  
    return totflow;  
}  

void dinic(){  
    while (bfs()) {  
        for(int i=S; i<=T; i++) 
        cur[i] = head[i];
        ans += dfs(S, INF);  
    }  
}  

int main(){
    scanf("%d%d", &n, &m);   
    S=0; T=n+m+1;  
    for(int i=1; i<=n; i++){   
        int cc; scanf("%d", &cc);  
        adde(i, T, cc); adde(T, i, 0);
    }  
    for(int i=1; i<=m; i++){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        sum += c;  
        adde(S, i + n, c);  
        adde(i + n, S, 0);  
        adde(i + n, a, INF); 
        adde(a, i + n, 0);   
        adde(i + n, b, INF); 
        adde(b, i + n, 0);   
    }  
    dinic();
    printf("%d\n", sum-ans);  
}
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页