最大获利
新的技术正冲击着手机通讯市场,对于各大运营商来说,这既是机遇,更是挑战。
THU 集团旗下的 CS&T 通讯公司在新一代通讯技术血战的前夜,需要做太多的准备工作,仅就站址选择一项,就需要完成前期市场研究、站址勘测、最优化等项目。
在前期市场调查和站址勘测之后,公司得到了一共 N N N 个可以作为通讯信号中转站的地址,而由于这些地址的地理位置差异,在不同的地方建造通讯中转站需 要投入的成本也是不一样的,所幸在前期调查之后这些都是已知数据:
建立第 i i i 个通讯中转站需要的成本为 P i ( 1 ≤ i ≤ N ) P_i(1 \le i \le N) Pi(1≤i≤N)。
另外公司调查得出了所有期望中的用户群,一共 M M M 个。
关于第 i i i 个用户群的信息概括为 A i , B i A_i, B_i Ai,Bi 和 C i C_i Ci:这些用户会使用中转站 A i A_i Ai 和中转站 B i B_i Bi 进行通讯,公司可以获益 C i 。( 1 ≤ i ≤ M , 1 ≤ A i , B i ≤ N ) C_i。(1 \le i \le M, 1 \le A_i, B_i \le N) Ci。(1≤i≤M,1≤Ai,Bi≤N)
THU 集团的 CS&T 公司可以有选择的建立一些中转站(投入成本),为一些用户提供服务并获得收益(获益之和)。
那么如何选择最终建立的中转站才能让公司的净获利最大呢?(净获利 = 获益之和 – 投入成本之和)
输入格式
第一行有两个正整数 N N N 和 M M M。
第二行中有 N N N 个整数描述每一个通讯中转站的建立成本,依次为 P 1 , P 2 , … , P N P_1, P_2, …, P_N P1,P2,…,PN 。
以下 M M M 行,第 ( i + 2 ) (i + 2) (i+2) 行的三个数 A i , B i A_i, B_i Ai,Bi 和 C i C_i Ci 描述第 i i i 个用户群的信息。
所有变量的含义可以参见题目描述。
输出格式
输出一个整数,表示公司可以得到的最大净获利。
数据范围
$1 \le N \le 5000$, $1 \le M \le 50000$, $0 \le C_i,P_i \le 100$输入样例:
5 5
1 2 3 4 5
1 2 3
2 3 4
1 3 3
1 4 2
4 5 3
输出样例:
4
样例解释
选择建立 1 、 2 、 3 1、2、3 1、2、3 号中转站,则需要投入成本 6 6 6,获利为 10 10 10,因此得到最大收益 4 4 4。
证明过程以及本题思路
概念:你可以选点,边可以不选全,但你如果选了一条边,那么边的两个点必须选。任何求如下图所示式子的最大值(就是很典型的 01 分数规划,因此得用二分来做)。
E
′
E'
E′ 指的是边,
V
′
V'
V′ 指的是点。
我们发现,最大密度子图可以转化成最大权闭合图,为什么呢?因为刚刚那个概念那么说,因此我们可以把边看成点,点看成点。但边数过多,因此我我们得优化。
如何优化呢?我们肯定是希望点里面的边数越多越好,所以我们把式子反过来。此时我们开始求边数,如上图所示,直接求很难,因此我们正难则反,我们求出所有点的度数减去上图标红部分就是里面的边数了(此时我们还采用将边一分为二的思想,因为每个边连接两个点)。
注意
V
′
V'
V′ 指的是密度子图。
所以能得到上述式子,其中
C
[
V
′
,
V
′
]
C[V',V']
C[V′,V′],其中
V
′
V'
V′ 拔是
V
′
V'
V′ 的补集。这样这种图就能跟最小割产生关系。
但不幸的是,我们算出的结果多了前面那一项很讨厌,因此我们得重构流网络,把前面的项删了或者合并。
此时我们发现:我们希望的是把蓝色的部分移到箭头指向的位置。
我们此时可以建立这样的图,其中为什么要加上
U
U
U,因为
2
g
−
d
i
2g-d_i
2g−di 可能是负数。
此时能推出右边的式子,右图下括号内的含义是所有边减去补集的边表示集合内部的边。(上图)
所以能算出如下式子,因为内部的边是无向边,因此有个系数 2 2 2。
那么我们的答案就是:(红色的框住的公式,如下图)
拓展:对于密度子图的边数是 2 ∣ E ∣ + ∣ V ∣ 2|E|+|V| 2∣E∣+∣V∣,最大闭合子图的边数是 3 ∣ E ∣ + ∣ V ∣ 3|E|+|V| 3∣E∣+∣V∣。
本题思路
- 现在我们要将本题和最大密度子图产生联系,由于密度子图的限制要求要想选某条边就必须要选该边对应的两个点,因此我们可以将用户群看作边,要想选某条边就必须选上这条边的两个点。
- 这就变成了一个密度子图的问题,然后本题是带点权和边权的,点权是负的,边权是正的,这里我们要最大化一个 ∣ E ′ ∣ + ∣ V ′ ∣ |E'|+|V'| ∣E′∣+∣V′∣,因为本道题既有点权也有边权,那么我们用的最大密度子图的 ∣ E ′ ∣ + ∣ V ′ ∣ − g ∣ V ′ ∣ |E'|+|V'|-g|V'| ∣E′∣+∣V′∣−g∣V′∣,这个公式,此时我们让 g = 0 g=0 g=0 ,那么就是求本道题的式子了。那么本道题的答案就是 U n − C [ S , T ] 2 \frac{Un-C[S,T]}{2} 2Un−C[S,T]。(少了个二分操作,因为 g = 0 g=0 g=0)
- 注意及拓展1:对于带点权和边权的最大密度子图,它的密度就变成了:
因此:式子变成
∣
E
′
∣
+
∣
V
′
∣
∣
v
′
∣
\frac{|E'|+|V'|}{|v'|}
∣v′∣∣E′∣+∣V′∣分子上的
∣
V
′
∣
|V'|
∣V′∣ 对应的是点权之和,分母上的
∣
v
′
∣
|v'|
∣v′∣ 对应的是点的个数,此时才称得上密度。但此时的答案式子保持不变。
- 拓展2:当只有点权的情况,如果有点权,此时我们就把 ∣ E ′ ∣ |E'| ∣E′∣ 改成 ∣ W ′ ∣ |W'| ∣W′∣。此时把度数改成权值,此时也满足半半规则,就是边权一半规则。
- 拓展3:总的来说,我们的点权相当于入度,边权就得特殊处理(即:变化式子)。
代码
//也就是说,每个用户必须跟两个基站绑定
//把用户标正,中转站标负
//而且所有用户只能在给定的基站内相互通信,因此这就是一个闭合子图
//现在我们要将本题和最大密度子图产生联系,由于密度子图的限制要求要想选某条边
//就必须要选该边对应的两个点,因此我们可以将用户群看作边,要想选某条边就必须
//选上这条边的两个点。
//但我们也发现,一个用户只能被两个基站绑定,因此这也是一个最大密度子图
//但本道题很特殊,它既有点权又有边权,因此我们用拓展的最后一个方法
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N = 55010,M = (50000*2+N)*2+10,INT = 1e8;
int e[M],ne[M],f[M],h[N],idx;
int cur[N],d[N],p[N];
int n,m,S,T;
int tot;
int din[N];
void add(int a,int b,int c1,int c2){
e[idx]=b,f[idx]=c1,ne[idx]=h[a],h[a]=idx++;
e[idx]=a,f[idx]=c2,ne[idx]=h[b],h[b]=idx++;
}
bool bfs(){
queue<int>q;
q.push(S);
memset(d,-1,sizeof d);
d[S]=0,cur[S]=h[S];
while(q.size()){
int t=q.front();
q.pop();
for(int i=h[t];~i;i=ne[i]){
int ver=e[i];
if(d[ver]==-1&&f[i]){
d[ver]=d[t]+1;
cur[ver]=h[ver];
if(ver==T)return true;
q.push(ver);
}
}
}
return false;
}
int find(int u,int lim){
if(u==T)return lim;
int flow=0;
for(int i=cur[u];~i&&flow<lim;i=ne[i]){
int ver=e[i];
cur[u]=i;
if(d[ver]==d[u]+1&&f[i]){
int t=find(ver,min(f[i],lim-flow));
if(!t)d[ver]=-1;
f[i]-=t,f[i^1]+=t,flow+=t;
}
}
return flow;
}
int dinic(){
int r=0,flow;
while(bfs())while(flow=find(S,INT))r+=flow;
return r;
}
int main(){
cin>>n>>m;
memset(h,-1,sizeof h);
S=0,T=n+1;
for(int i=1;i<=n;i++)cin>>p[i],p[i]*=-1;//消耗品,直接设为负
for(int i=1;i<=m;i++){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c,c);
din[a]+=c,din[b]+=c;//此时的边权不是入度
}
int U=0;
for(int i=1;i<=n;i++)U=max(U,2*p[i]+din[i]);
for(int i=1;i<=n;i++){
add(S,i,U,0);
add(i,T,U-2*p[i]-din[i],0);
}
cout<<(U*n-dinic())/2;
return 0;
}