蓝桥杯 buying sets求闭合子图最小,最大权

题目:

给定n个集合, 要求选出其中某些集合, 使得这些集合的并集的势, 等于选出的集合的数目.对于任意的k(1<=k<=n), 满从中选出任意k个集合, 这k个集合的并集的势一定大于等于k.每个集合有一个权值, 每个选择方案的代价是所选的集合的权值的和.请输出代价最小的选择方案的代价.当然, 不选择任何一个集合是一个可行的方案(权值和为0), 但不一定最优(权值和可以为负).

第一行一个正整数n(1<=n<=300), 为集合个数.
  在接下来n行中, 第i行描述第i个集合:
  首先给出一个正整数m[i]为该集合的势, 显然1<=m[i]<=n.
  接下来m[i]个在1到n之间的整数, 表示该集合中的元素.
  最后一行n个整数, 为每个集合的权值, 绝对值不超过1e6.

分析:

 

闭合图:对于一个有向图G,存在点集合V,任取点u属于V,u的出边的另一个点也属于V,则为闭合图。
            任取一起点,终点必定是无出度的点。
最大权闭合子图:当每个点有一个权值w(有正有负),点权和最大的闭合图为最大权闭合子图。

求最大权闭合子图:这个问题可以转化为最小割问题,用网络流解决。从源点s向每个正权点连一条容量为权值的边,每个负  权点向汇点t连一条容量为权值的绝对值的边,有向图原来的边容量全部为无限大。求它的最小割,割掉后,与源点s仍连通的点构成最大权闭合子图(s-t割中的s集合),最大权值为正权值之和-最小割,注意在数值上,最大流和最小割是相等的,所以建立图后求最大流即可.

题目分析:题目中说任选k个集合的并集的势(元素个数)一定大于等于k,又任意元素都是大于等于1并小于等于n.若选所有n个集合的并集的势一定等于n,可以发现集合和元素是完美匹配的,即把集合标号与元素 作为一张二分图,一定存在一个完美匹配.一个集合对应一个元素,选取一个集合则其中的元素对应的集合也必须选取,则问题就变成了最小权闭合子图,可以把权值取反,正权点当做负权点,负权点当做正权点,套上最大权模板来做 ,求出最大权值后取相反数就是答案.

#include<iostream>
#include<vector>
#include<cstring>
#include<queue>
#include<cstdio>
using namespace std;
const int MAXN=305;
const int INF=1<<30;
int setnum;
vector<int> sets[MAXN];
int weight[MAXN];
int match[MAXN];
int visited[MAXN];

int posweight=0;

struct Edge{
	int from,to,cap,flow;
};
vector<int> G[MAXN];
vector<Edge> edges;
int deep[MAXN];
bool dfs(int u){
	for(int i=0;i<sets[u].size();i++){
		int v=sets[u][i];
		if(visited[v])continue;
		visited[v]=1;
		if(!match[v]||dfs(match[v])){
			match[v]=u;
			return true;
		}
	}
	return false;
}
void hungary(){//匈牙利算法找到完美匹配
	memset(match,0,sizeof(match));
	for(int i=1;i<=setnum;i++){
		memset(visited,0,sizeof(visited));
		dfs(i);
	}
}

void addedge(int u,int v,int cap){
	edges.push_back(Edge{u,v,cap,0});
	edges.push_back(Edge{v,u,0,0});
	int m=edges.size();
	G[u].push_back(m-2);
	G[v].push_back(m-1);
}

void init(){//根据匈牙利算法找到的完美匹配建立搜索图,0为源点,setnum+1为汇点
	hungary();
	for(int i=1;i<=setnum;i++)
     if(weight[i]<0){addedge(0,i,-weight[i]);posweight-=weight[i];}//权值取反,负权点当正权点,正权点当负权点
     else addedge(i,setnum+1,weight[i]);
	for(int i=1;i<=setnum;i++){
		for(int j=0;j<sets[i].size();j++){
			int v=sets[i][j];//取出相应集合中的元素
			if(match[v]!=i)addedge(i,match[v],INF);
		}
	}
}
bool bfs(){//bfs分层次图
	memset(deep,0,sizeof(deep));
	queue<int> Q;
	Q.push(0);//入队源点
	deep[0]=1;
	while(!Q.empty()){
		int u=Q.front();
		Q.pop();
		for(int i=0;i<G[u].size();i++){
			Edge edge=edges[G[u][i]];
			if(!deep[edge.to]&&edge.cap>edge.flow){
				deep[edge.to]=deep[edge.from]+1;
				Q.push(edge.to);
			}
		}
	}
	return deep[setnum+1];
}
int dinc(int u,int pref){//dinc求最小割即最大流
	if(u==setnum+1)return pref;//找到源点
	int f=0;
	for(int i=0;i<G[u].size();i++){
		int t=G[u][i];
		Edge edge=edges[t];
		if(edge.cap>edge.flow&&deep[edge.from]==deep[edge.to]-1){//有残量可以增广并且深度符合
			int temp=min(pref,edge.cap-edge.flow);
			int x=dinc(edge.to,temp);
			edges[t].flow+=x;
			edges[t^1].flow-=x;
			f+=x;
			pref-=x;
		}
	}
	return f;
}
int solve(){//求闭合图最大权值
	init();
	int maxflow=0;
	while(bfs()){
		maxflow+=dinc(0,1<<30);
	}
	return posweight-maxflow;
}
int main(){
	cin>>setnum;
	int k,ele;
	for(int i=1;i<=setnum;i++){
		cin>>k;
		for(int j=0;j<k;j++){
			cin>>ele;
			sets[i].push_back(ele);
		}
	}
	for(int i=1;i<=setnum;i++)
		cin>>weight[i];
	int ans=-solve();//因为权值取反了,最小权值就是最大权值的相反数
	if(ans>0)cout<<0<<endl;
	else cout<<ans<<endl;
	return 0;
}

 


版权声明:本文为qq_40678690原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
THE END
< <上一篇
下一篇>>