zoukankan      html  css  js  c++  java
  • 最大权闭合子图

    最大权闭合子图

    闭合子图

    定义有向图的一个闭合子图是该有向图的一个点集,其中这个点集中的所有点的出边连向的还是点集中的点。

    最大权闭合子图

    给有向图的点加一个点权,能得到的点权最大的闭合子图。

    网络流模型

    • 将所有正点权的点的点权全部加入答案。接下来我们将原来正点权的点变成负点权。现在图上全是负点,我们需要尽量选最少点权使图符合条件。
    • 设超级源点和超级汇点。
    • 将超级源向所有原来是正点权的点连边,流量为其点权绝对值。
    • 将所有负点权的点向超级汇连边,流量为其点权绝对值。
    • 将所有原图上的边加入,流量为无限。
    • 接下来跑最小割。可以证明此时最小割就是使图合法的最小代价。感性理解一下,将源点与汇点割开的最小割,因为原图的边的流量是无限,只能将超级源汇连出去的边割开。相当于就是不选某些正点或选择某些汇点。这也就保证了后面的点选择时前面的点一定会被选。
    • 最后用前面的答案减去最小割即可。
    • 如果需要构造方案,请看下例题。

    P2762 太空飞行计划问题

    题意

    给你两个集合,一个集合所有元素有正点权,另一个有负点权,前一个集合的每一个元素有若干后一个集合的必选后继,要求若选择该元素后继必选,求能得到的最大点权和。

    思路

    最大权闭合子图模板。将前一个集合的所有元素向其所有后继连边,就变成了一个有向图,然后用上述方法建模即可。

    关于读入

    我使用了快读来读入换行符。其代码如下:

    inline int read(){
        int w=0,x=0;static char c(0);
        while(!isdigit(c) and c!='
    ') w|=c=='-',c=getchar();
        if(c=='
    ')return c=getchar(),INF;
        while(isdigit(c))x=x*10+(c^48),c=getchar();
        return w?-x:x;
    }
    

    其中,利用了 static 来保证之前读入的字符能够重新判断。在这个函数中,如果读到换行符将会返回 INF。

    关于输出方案

    在这道题中,思考网络流建模方式即可得出,如果在最后一次 bfs 中一个正点权的点可以到达,说明其没有被取消选择,即被选择了;如果一个负点权的点可以到达,说明其被选择了。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cctype>
    #include<cstring>
    #include<cmath>
    using namespace std;
    namespace star
    {
    	const int maxn=105,maxm=2600,s=103,t=104,INF=0x3f3f3f3f;
    	inline int read(){
    		int w=0,x=0;static char c(0);
    		while(!isdigit(c) and c!='
    ') w|=c=='-',c=getchar();
    		if(c=='
    ')return c=getchar(),INF;
    		while(isdigit(c))x=x*10+(c^48),c=getchar();
    		return w?-x:x;
    	}
    	int n,m,val[maxn],ans;
    	int ecnt=1,head[maxn],cur[maxn],to[maxm<<1],nxt[maxm<<1],w[maxm<<1];
    	inline void addedge(int a,int b,int c){
    		to[++ecnt]=b,nxt[ecnt]=head[a],head[a]=ecnt,w[ecnt]=c;
    		to[++ecnt]=a,nxt[ecnt]=head[b],head[b]=ecnt,w[ecnt]=0;
    	}
    	int dep[maxn];
    	inline bool bfs(){
    		memset(dep,-1,sizeof dep);
    		static int q[maxn];
    		int hd=0,tl=1;
    		q[tl]=s,dep[s]=0;
    		while(hd<tl){
    			int x=q[++hd];
    			for(int u,i=head[x];i;i=nxt[i]) if(dep[u=to[i]]==-1 and w[i])
    				dep[u]=dep[x]+1,q[++tl]=u;
    		}
    		return dep[t]!=-1;
    	}
    	int dfs(int x,int flow){
    		if(x==t)return flow;
    		int used=0,u;
    		for(int& i=cur[x];i;i=nxt[i]) if(dep[u=to[i]]==dep[x]+1 and w[i]){
    			int v=dfs(u,min(flow-used,w[i]));
    			used+=v,w[i]-=v,w[i^1]+=v;
    			if(used==flow) break;
    		}
    		return used;
    	}
    	inline int dinic(){
    		int ans=0;
    		while(bfs()) memcpy(cur,head,sizeof cur),ans+=dfs(s,INF);
    		return ans;
    	}
    	inline void work(){
    		m=read(),n=read();read();
    		for(int v,i=1;i<=m;i++){
    			addedge(s,i,v=read());ans+=v;
    			while((v=read())!=INF) addedge(i,v+m,INF);
    		}
    		for(int i=1;i<=n;i++) addedge(i+m,t,read());
    		ans-=dinic();
    		for(int i=1;i<=m;i++) if(dep[i]!=-1) printf("%d ",i);puts("");
    		for(int i=1;i<=n;i++) if(dep[m+i]!=-1) printf("%d ",i);
    		printf("
    %d
    ",ans);
    	}
    }
    signed main(){
    	star::work();
    	return 0;
    }
    

    CF103E Buying Sets

    题意

    有一个大小为 n 的全集,每个元素是一个数,有 n 个子集。题目保证任意 k 个子集的并的大小 ⩾ k。

    每个子集有一个可正可负的权值,你需要选出一些子集使得这些子集并的大小等于子集个数,且所选子集的权值和最小。可以为空集。

    思路

    发现如果没有 子集并的大小等于子集个数 的限制,将边权取负就是最大权闭合子图的模板。考虑怎么满足这个限制。

    我们把每个节点的点权加上一个巨大的偏移量 (Delta) 即可。

    原因是,如果跑最大流时要多选一个点的话,代价一定会变得很大。因为题目保证 任意 k 个子集的并的大小 ⩾ k,而且可以选空集,所以我们可以这么干。

    早期代码

  • 相关阅读:
    用Latex编辑数学公式
    《利用Python进行数据分析》学习笔记之Matplotlib : pandas中的绘图函数
    《利用Python进行数据分析》学习笔记之Pandas基础
    splice()使用
    markdown语法
    【Docker】Docker 镜像
    【Docker】Docker 命令
    【Docker】Docker 安装
    【Docker】Docker 简介
    【MySQL 高级】知识拓展
  • 原文地址:https://www.cnblogs.com/BrotherHood/p/14291144.html
Copyright © 2011-2022 走看看