概念
选择一个子图 G ′ = ( V ′ , E ′ ) G'=(V',E') G′=(V′,E′),其中对于任意一条边的两个端点必须在所选的点集中,最大化 ∣ E ′ ∣ ∣ V ′ ∣ \frac{|E'|}{|V'|} ∣V′∣∣E′∣
做法
利用01分数规划二分即最大化
∣ E ′ ∣ − g ∣ V ′ ∣ |E'|-g|V'| ∣E′∣−g∣V′∣
也就是最小化 g ∣ V ′ ∣ − ∣ E ′ ∣ = ∑ v ∈ V ′ g − ∑ v ∈ V ′ 1 = ∑ v ∈ V ′ g − 1 2 ( ∑ v ∈ V ′ d v − c [ V ′ , V ′ ˉ ] ) = 1 2 ( ∑ v ∈ V ′ ( 2 g − d v ) + c [ V ′ , V ′ ˉ ] ) g|V'|-|E'|=\sum_{v\in V'}g-\sum_{v\in V'}1=\sum_{v\in V'}g-\frac{1}{2}(\sum_{v\in V'}d_v-c[V',\bar{V'}])=\frac 1 2(\sum_{v\in V'}(2g-d_v)+c[V',\bar{V'}]) g∣V′∣−∣E′∣=v∈V′∑g−v∈V′∑1=v∈V′∑g−21(v∈V′∑dv−c[V′,V′ˉ])=21(v∈V′∑(2g−dv)+c[V′,V′ˉ])
我们发现除了割边还有一个东西即 ∑ v ∈ V ′ ( 2 g − d v ) \sum_{v\in V'}(2g-d_v) ∑v∈V′(2g−dv),只需要将每个点连向汇点一条容量是 2 g − d v 2g-d_v 2g−dv的边即可让构建的割的容量是上述式子。因为对于 V ′ V' V′中的点要求与源点 S S S放在一起,那么只要它与汇点存在边就会对割的容量由贡献。
建图:原图中的点与边的容量都是1,再加上所有点向汇点连边容量是 2 g − d v 2g-d_v 2g−dv。
那么构建出的网络流的割的容量 c [ S , T ] = 2 ( g ∣ V ′ ∣ − ∣ E ′ ∣ ) c[S,T]=2(g|V'|-|E'|) c[S,T]=2(g∣V′∣−∣E′∣)
于是 g ∣ V ′ ∣ − ∣ E ′ ∣ = 1 2 c [ S , T ] g|V'|-|E'|=\frac{1}{2}c[S,T] g∣V′∣−∣E′∣=21c[S,T]
即求最小割。
为了防止连向汇点的边权 2 g − d v 2g-d_v 2g−dv是负值,需要增添一个偏移量 U U U
重新建图即:①源点向每个点连边,容量是 U U U;②原图中的边,容量是1;③每个点向汇点连边,容量是 2 g − d v + U 2g-d_v+U 2g−dv+U
可证 g ∣ V ′ ∣ − ∣ E ′ ∣ = 1 2 ( c [ S , T ] − n U ) g|V'|-|E'|=\frac{1}{2}(c[S,T]-nU) g∣V′∣−∣E′∣=21(c[S,T]−nU)
最初需要最大化的值即 ∣ E ′ ∣ − g ∣ V ′ ∣ = n U − c [ S , T ] 2 |E'|-g|V'|=\frac{nU-c[S,T]}{2} ∣E′∣−g∣V′∣=2nU−c[S,T]
不难看出如果我们这里把边看成点,选边必须选择两个端点的限制可以加到边上即边向两个端点连单向边即可以转化为最大权闭合图问题
类型 | 建图点数 | 建图边数 |
---|---|---|
最大密度子图 | |V| | 2|V|+|E| |
最大权闭合图 | |V|+|E| | |V|+3|E| |
如果存在边权密度: ∑ e ∈ E W e ∣ V ∣ \frac{\sum_{e\in E} W_e}{|V|} ∣V∣∑e∈EWe
d v d_v dv记为该点出边权值和而不是出度即可
若存在点权和边权密度 ∑ v ∈ V p v + ∑ e ∈ E W e ∣ V ∣ \frac{\sum_{v\in V}p_v+\sum_{e\in E} W_e}{|V|} ∣V∣∑v∈Vpv+∑e∈EWe
① d v d_v dv记为该点出边权值和
②连向汇点和源点的边容量是 2 g − d v − 2 p v + U 2g-d_v-2p_v+U 2g−dv−2pv+U
例题
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=5010,M=(50000+2*N)*2+10,INF=0x3f3f3f3f;
int n,m;
int h[N],e[M],ne[M],f[M],idx;
int S,T,d[N],q[N],cur[N];
int deg[N],p[N];
void add(int a,int b,int c1,int c2)
{
e[idx]=b,ne[idx]=h[a],f[idx]=c1,h[a]=idx++;
e[idx]=a,ne[idx]=h[b],f[idx]=c2,h[b]=idx++;
}
bool bfs()
{
memset(d,-1,sizeof d);
int tt=0,hh=0;
q[S]=0,cur[S]=h[S],d[S]=0;
while(hh<=tt)
{
int t=q[hh++];
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if(d[j]==-1&&f[i])
{
d[j]=d[t]+1;
cur[j]=h[j];
if(j==T) return 1;
q[++tt]=j;
}
}
}
return 0;
}
int dfs(int u=S,int flow=INF)
{
if(u==T) return flow;
int rmn=flow;
for(int i=cur[u];i!=-1&&rmn;i=ne[i])
{
cur[u]=i;
int j=e[i];
if(d[j]==d[u]+1&&f[i])
{
int t=dfs(j,min(f[i],rmn));
if(!t) d[j]=-1;
f[i]-=t,f[i^1]+=t,rmn-=t;
}
}
return flow-rmn;
}
int dinic()
{
int r=0;
while(bfs()) r+=dfs();
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;
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c,c);
deg[a]+=c,deg[b]+=c;//记录出边权值和
}
int U=0;
for(int i=1;i<=n;i++) U=max(U,2*p[i]+deg[i]);
for(int i=1;i<=n;i++) add(S,i,U,0),add(i,T,U-2*p[i]-deg[i],0);
cout<<(U*n-dinic())/2<<'\n';
return 0;
}