题目链接
新的技术正冲击着手机通讯市场,对于各大运营商来说,这既是机遇,更是挑战。
THU 集团旗下的 CS&T 通讯公司在新一代通讯技术血战的前夜,需要做太多的准备工作,仅就站址选择一项,就需要完成前期市场研究、站址勘测、最优化等项目。
在前期市场调查和站址勘测之后,公司得到了一共 N N 个可以作为通讯信号中转站的地址,而由于这些地址的地理位置差异,在不同的地方建造通讯中转站需 要投入的成本也是不一样的,所幸在前期调查之后这些都是已知数据:
建立第 i i 个通讯中转站需要的成本为 P i ( 1 ≤ i ≤ N ) P i ( 1 ≤ i ≤ N ) 。
另外公司调查得出了所有期望中的用户群,一共 M M 个。
关于第 i i 个用户群的信息概括为 A i , B i A i , B i 和 C i C i :这些用户会使用中转站 A i A i 和中转站 B i B i 进行通讯,公司可以获益 C i 。 ( 1 ≤ i ≤ M , 1 ≤ A i , B i ≤ N ) C i 。 ( 1 ≤ i ≤ M , 1 ≤ A i , B i ≤ N )
THU 集团的 CS&T 公司可以有选择的建立一些中转站(投入成本),为一些用户提供服务并获得收益(获益之和)。
那么如何选择最终建立的中转站才能让公司的净获利最大呢?(净获利 = 获益之和 – 投入成本之和)
输入格式
第一行有两个正整数 N N 和 M M 。
第二行中有 N N 个整数描述每一个通讯中转站的建立成本,依次为 P 1 , P 2 , … , P N P 1 , P 2 , … , P N 。
以下 M M 行,第 ( i + 2 ) ( i + 2 ) 行的三个数 A i , B i A i , B i 和 C i C i 描述第 i i 个用户群的信息。
所有变量的含义可以参见题目描述。
输出格式
输出一个整数,表示公司可以得到的最大净获利。
数据范围
1 ≤ N ≤ 5000 1 ≤ N ≤ 5000 ,
1 ≤ M ≤ 50000 1 ≤ M ≤ 50000 ,
0 ≤ C i , P i ≤ 100 0 ≤ C i , P i ≤ 100
输入样例:
输出样例:
样例解释
选择建立 1 、 2 、 3 1 、 2 、 3 号中转站,则需要投入成本 6 6 ,获利为 10 10 ,因此得到最大收益 4 4 。
解题思路
最小割,最大权闭合图
闭合子图(本质上是一个点集):对于一个图 G ( V , E ) G ( V , E ) ,找到图中的某个点集,这个点集满足不存在点集中的某个点指向除点集以外的其他点的边
最大权闭合子图:即闭合子图权值最大的子图
对于一个含有点权的图,考虑转化为流网络,建立源点 s s ,汇点 t t ,s s 向所有权值为正的点连边,容量为点权,所有点权为负的点向 t t 连边,容量为点权的相反数,其余点之间按原图连边,容量足够大
在上述定义的流网络的基础上,定义简单割 [ S , T ] [ S , T ] :S S 和 T T 之间的边不含容量足够大的边。显然,最小割一定是简单割:由最小割最大流定理,最 小 割 = 最 大 流 最 小 割 = 最 大 流 ,由可行流的定理,由 s s 出去的边肯定没有容量足够大的边,故最小割一定是简单割
给出一个结论:流网络中的简单割和原图中的闭合子图一一对应
证明:
对于原图上的任意一个闭合子图都可以对应流网络上的一个简单割
对于闭合子图的任意一个子集 V ′ V ′ ,构造一个割集 [ S , T ] [ S , T ] ,其中 S = V ′ + { s } S = V ′ + { s } ,T = V − S T = V − S (其中全点集 V V 包括 s s 和 t t ),假设 S S 到 T T 中存在容量足够大的边,则由于 s s 连接的都是容量有限的边,要是存在这样的边则一定是原图的闭合子图连接过去的,而由于连接 t t 的边容量也是有限的,故连接到 T T 部分的点也不会是 t t ,故边上的两个点如果可以的话一定是原图上的两个点,由闭合子图的定义,闭合子图不能向除自己点集以外的其他点连边,故这样构造的割集 S S 和 T T 之间不可能存在容量足够大的边,即该割是简单割
对于流网络上的任意一个简单割都可以对应原图上的一个闭合子图
对于任意一个简单割 [ S , T ] [ S , T ] ,即证明 S − s S − s 对应原图是一个闭合子图,即里面的点不能向 T T 中除 t t 以外的其他点连边,显然,简单割 S S 和 T T 之间的边的容量都有限,即都会跟 s s 和 t t 有关,S S 和 T T 之间的边一个点要么是 s s ,一个点要么是 t t ,故 S S 里面的点不可能向 T T 中除 t t 以外的其他点连边,得证
对于任意一个简单割 [ S , T ] [ S , T ] ,S S 中的闭合子图为 V ′ V ′ ,设原图点集为 V V ,C [ S , T ] = C v ∈ V ′ [ v , t ] + C v ∈ V − V ′ [ s , v ] = ∑ v ∈ V ′ , v 为 负 权 点 − w v + ∑ v ∈ V − V ′ , v 为 正 权 点 w v C [ S , T ] = C v ∈ V ′ [ v , t ] + C v ∈ V − V ′ [ s , v ] = ∑ v ∈ V ′ , v 为 负 权 点 − w v + ∑ v ∈ V − V ′ , v 为 正 权 点 w v ,设 W + W + 为原图中所有权值为正的权值之和,则 W + = ∑ v ∈ V − V ′ , v 为 正 权 点 w v + ∑ v ∈ V ′ , v 为 正 权 点 w v W + = ∑ v ∈ V − V ′ , v 为 正 权 点 w v + ∑ v ∈ V ′ , v 为 正 权 点 w v ,则 ∑ v ∈ V − V ′ , v 为 正 权 点 w v = W + − ∑ v ∈ V ′ , v 为 正 权 点 w v ∑ v ∈ V − V ′ , v 为 正 权 点 w v = W + − ∑ v ∈ V ′ , v 为 正 权 点 w v ,则 C [ S , T ] = ∑ v ∈ V ′ , v 为 负 权 点 − w v + W + − ∑ v ∈ V ′ , v 为 正 权 点 w v C [ S , T ] = ∑ v ∈ V ′ , v 为 负 权 点 − w v + W + − ∑ v ∈ V ′ , v 为 正 权 点 w v ,则 ∑ v ∈ V ′ w v = W + − C [ S , T ] ∑ v ∈ V ′ w v = W + − C [ S , T ] ,要使 ∑ v ∈ V ′ w v ∑ v ∈ V ′ w v 最大,即 [ S , T ] [ S , T ] 最小,即最小割
本题为所有正点减去与之相关的负点,不妨这样建图:建立源点 s s 和汇点 t t ,源点向所有点权为正的点连边,容量为点权,所有点权为负的点向 t t 连边,容量为点权的相反数,选择一定点权为正的点,需向两个点权为负的点两边,容量足够大,这样可以对应上一个闭合图,为 什 么 ? 为 什 么 ? 闭合图是指选择的点集,不存在向外连的边,假设选择了正权的点,则其必须得选其连向的负权的点,正好满足题目要求
最大密度子图
在 2324. 生活的艰辛 的扩展(即点权和边权都有的情况)中,要使 | E | + | V | − g × c n t V | E | + | V | − g × c n t V (这里将所有点权置为负数,边权即顾客点权,为正) 最大化,对应本题使 | E | + | V | | E | + | V | 最大,此时 g = 0 g = 0 ,即可将问题转化为最大密度子图问题
代码
#include <bits/stdc++.h>
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair<int , int > PII;
typedef pair<LL, LL> PLL;
template <typename T> bool chkMax (T &x, T y) { return (y > x) ? x = y, 1 : 0 ; }
template <typename T> bool chkMin (T &x, T y) { return (y < x) ? x = y, 1 : 0 ; }
template <typename T> void inline read (T &x) {
int f = 1 ; x = 0 ; char s = getchar ();
while (s < '0' || s > '9' ) { if (s == '-' ) f = -1 ; s = getchar (); }
while (s <= '9' && s >= '0' ) x = x * 10 + (s ^ 48 ), s = getchar ();
x *= f;
}
const int N=55005 ,M=(3 *50005 +5005 )*2 ,inf=1e9 ;
int n,m,S,T;
int h[N],ne[M],e[M],f[M],idx;
int hh,tt,q[N],cur[N],d[N];
void add (int a,int b,int c)
{
e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;
e[idx]=a,f[idx]=0 ,ne[idx]=h[b],h[b]=idx++;
}
bool bfs ()
{
memset (d,-1 ,sizeof d);
d[S]=hh=tt=0 ;
cur[S]=h[S];
q[0 ]=S;
while (hh<=tt)
{
int x=q[hh++];
for (int i=h[x];~i;i=ne[i])
{
int y=e[i];
if (d[y]==-1 &&f[i])
{
d[y]=d[x]+1 ;
cur[y]=h[y];
if (y==T)return true ;
q[++tt]=y;
}
}
}
return false ;
}
int dfs (int x,int limit)
{
if (x==T)return limit;
int flow=0 ;
for (int i=cur[x];~i&&flow<limit;i=ne[i])
{
cur[x]=i;
int y=e[i];
if (d[y]==d[x]+1 &&f[i])
{
int t=dfs (y,min (f[i],limit-flow));
if (!t)d[y]=-1 ;
f[i]-=t,f[i^1 ]+=t,flow+=t;
}
}
return flow;
}
int dinic ()
{
int res=0 ,flow;
while (bfs ())while (flow=dfs (S,inf))res+=flow;
return res;
}
int main ()
{
memset (h,-1 ,sizeof h);
scanf ("%d%d" ,&n,&m);
S=0 ,T=n+m+1 ;
for (int i=1 ;i<=n;i++)
{
int p;
scanf ("%d" ,&p);
add (i,T,p);
}
int res=0 ;
for (int i=1 ;i<=m;i++)
{
int a,b,c;
scanf ("%d%d%d" ,&a,&b,&c);
res+=c;
add (S,n+i,c),add (n+i,a,inf),add (n+i,b,inf);
}
printf ("%d" ,res-dinic ());
return 0 ;
}
#include <bits/stdc++.h>
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair<int , int > PII;
typedef pair<LL, LL> PLL;
template <typename T> bool chkMax (T &x, T y) { return (y > x) ? x = y, 1 : 0 ; }
template <typename T> bool chkMin (T &x, T y) { return (y < x) ? x = y, 1 : 0 ; }
template <typename T> void inline read (T &x) {
int f = 1 ; x = 0 ; char s = getchar ();
while (s < '0' || s > '9' ) { if (s == '-' ) f = -1 ; s = getchar (); }
while (s <= '9' && s >= '0' ) x = x * 10 + (s ^ 48 ), s = getchar ();
x *= f;
}
const int N=5005 ,M=(2 *N+50005 )*2 ,inf=1e9 ;
int n,m,S,T;
int h[N],ne[M],f[M],e[M],idx;
int p[N],deg[N];
int d[N],hh,tt,q[N],cur[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 ()
{
memset (d,-1 ,sizeof d);
d[S]=hh=tt=0 ;
q[0 ]=S;
cur[S]=h[S];
while (hh<=tt)
{
int x=q[hh++];
for (int i=h[x];~i;i=ne[i])
{
int y=e[i];
if (d[y]==-1 &&f[i])
{
d[y]=d[x]+1 ;
cur[y]=h[y];
if (y==T)return true ;
q[++tt]=y;
}
}
}
return false ;
}
int dfs (int x,int limit)
{
if (x==T)return limit;
int flow=0 ;
for (int i=cur[x];~i&&flow<limit;i=ne[i])
{
cur[x]=i;
int y=e[i];
if (d[y]==d[x]+1 &&f[i])
{
int t=dfs (y,min (f[i],limit-flow));
if (!t)d[y]=-1 ;
f[i]-=t,f[i^1 ]+=t,flow+=t;
}
}
return flow;
}
int dinic ()
{
int res=0 ,flow;
while (bfs ())while (flow=dfs (S,inf))res+=flow;
return res;
}
int main ()
{
memset (h,-1 ,sizeof h);
scanf ("%d%d" ,&n,&m);
for (int i=1 ;i<=n;i++)scanf ("%d" ,&p[i]),p[i]*=-1 ;
for (int i=1 ;i<=m;i++)
{
int a,b,c;
scanf ("%d%d%d" ,&a,&b,&c);
add (a,b,c,c);
deg[a]+=c,deg[b]+=c;
}
S=0 ,T=n+1 ;
int U=0 ;
for (int i=1 ;i<=n;i++)U=max (U,deg[i]+2 *p[i]);
for (int i=1 ;i<=n;i++)
{
add (i,T,U-deg[i]-2 *p[i],0 );
add (S,i,U,0 );
}
printf ("%d" ,(n*U-dinic ())/2 );
return 0 ;
}
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!