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);
}