【模板】最大密度子图

ACM模板


概念

选择一个子图 G ′ = ( V ′ , E ′ ) G'=(V',E') G=(V,E),其中对于任意一条边的两个端点必须在所选的点集中,最大化 ∣ E ′ ∣ ∣ V ′ ∣ \frac{|E'|}{|V'|} VE

做法

利用01分数规划二分即最大化
∣ E ′ ∣ − g ∣ V ′ ∣ |E'|-g|V'| EgV
也就是最小化 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'}]) gVE=vVgvV1=vVg21(vVdvc[V,Vˉ])=21(vV(2gdv)+c[V,Vˉ])
我们发现除了割边还有一个东西即 ∑ v ∈ V ′ ( 2 g − d v ) \sum_{v\in V'}(2g-d_v) vV(2gdv),只需要将每个点连向汇点一条容量是 2 g − d v 2g-d_v 2gdv的边即可让构建的割的容量是上述式子。因为对于 V ′ V' V中的点要求与源点 S S S放在一起,那么只要它与汇点存在边就会对割的容量由贡献。

建图:原图中的点与边的容量都是1,再加上所有点向汇点连边容量是 2 g − d v 2g-d_v 2gdv
那么构建出的网络流的割的容量 c [ S , T ] = 2 ( g ∣ V ′ ∣ − ∣ E ′ ∣ ) c[S,T]=2(g|V'|-|E'|) c[S,T]=2(gVE)
于是 g ∣ V ′ ∣ − ∣ E ′ ∣ = 1 2 c [ S , T ] g|V'|-|E'|=\frac{1}{2}c[S,T] gVE=21c[S,T]
即求最小割。

为了防止连向汇点的边权 2 g − d v 2g-d_v 2gdv是负值,需要增添一个偏移量 U U U
重新建图即:①源点向每个点连边,容量是 U U U;②原图中的边,容量是1;③每个点向汇点连边,容量是 2 g − d v + U 2g-d_v+U 2gdv+U
可证 g ∣ V ′ ∣ − ∣ E ′ ∣ = 1 2 ( c [ S , T ] − n U ) g|V'|-|E'|=\frac{1}{2}(c[S,T]-nU) gVE=21(c[S,T]nU)

最初需要最大化的值即 ∣ E ′ ∣ − g ∣ V ′ ∣ = n U − c [ S , T ] 2 |E'|-g|V'|=\frac{nU-c[S,T]}{2} EgV=2nUc[S,T]

不难看出如果我们这里把边看成点,选边必须选择两个端点的限制可以加到边上即边向两个端点连单向边即可以转化为最大权闭合图问题

类型建图点数建图边数
最大密度子图|V|2|V|+|E|
最大权闭合图|V|+|E||V|+3|E|

如果存在边权密度: ∑ e ∈ E W e ∣ V ∣ \frac{\sum_{e\in E} W_e}{|V|} VeEWe
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|} VvVpv+eEWe
d v d_v dv记为该点出边权值和
②连向汇点和源点的边容量是 2 g − d v − 2 p v + U 2g-d_v-2p_v+U 2gdv2pv+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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用函数模板最大值的方法是定义一个函数模板,该函数模板可以接受任意数量、任意类型的参数,并返回这些参数中的最大值。 以下是一个求最大值的函数模板的示例代码: ```cpp template<typename T> T maxele(T a) { return a; } template<typename T, typename... Args> T maxele(T a, Args... arg) { return max(a, maxele(arg...)); } ``` 在这个示例代码中,`maxele`函数模板有两个重载版本。第一个版本接受一个参数,直接返回该参数。第二个版本接受一个或多个参数,并使用递归调用来比较参数的大小,最终返回最大值。 你可以根据需要调用这个函数模板来求任意数量、任意类型的参数的最大值。例如,你可以使用整型或双精度类型的数组来调用这个函数模板,如下所示: ```cpp int intArray[] = {1, 2, 3, 4, 5}; double doubleArray[] = {1.1, 2.2, 3.3, 4.4, 5.5}; int maxInt = maxele(intArray<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [C++可变参模板使用(实现求最大值)、tuple实现、type_traits的使用、去除const模板的使用、initializer_list...](https://blog.csdn.net/w_weixiaotao/article/details/108519383)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [c++知识点---函数模板实现求数组的最大值](https://blog.csdn.net/tobe_numberone/article/details/78149922)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值