codeforces103E Buying Sets

本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作。

本文作者:ljh2000
作者博客:http://www.cnblogs.com/ljh2000-jump/
转载请注明出处,侵权必究,保留最终解释权!

题目链接:codeforces103E

正解:网络流+最小权闭合图

解题报告:

  考虑这道题的模型。

  首先因为存在完备匹配,所以可以给每个点找一个匹配点,然后这个点向所需要的药材的匹配点连边。

  然后我们可以得到一个选择一个点就要选择它的所有出边的最小闭合权子图的模型,取个负号跑最大权闭合子图就好了,直接套板子。

  这样建图的正确性可以意会一下,很明显可以起到选一个就选另一个的效果。

  补一发最大权闭合子图的理解:

  很久以前学过但是忘得差不多了。

  这类题的模型就是一个每个点带权的有向图,选择了一个点就要选择他的所有出边,最大化选出的点的最大权值和。

  建图的方法就是$S$向正权点连容量为点权的边,原图的边容量为$inf$,负权点向$T$连容量为点权的绝对值的边。

  先默认选择所有的正权点,$ans$的初值就是正权点的点权之和,而实际情况下可能有些正权点不选,还要另选择一些负权点。

  那么$ans=ans-$(不选择正权$+$选择的负权的绝对值),后者就是图的最小割,也就是最大流。

  考虑最小割只能与$S$或$T$相连。

  与$S$相连的点说明供应$<=$出边流量,那么显然是不选择的正权,因为选了不划算;

  与$T$相连的说明,为其提供流量的边还未满,那么这个负权就是选择了正权之后付出的一些负权点代价,显然会划算。

//It is made by ljh2000
//有志者,事竟成,破釜沉舟,百二秦关终属楚;苦心人,天不负,卧薪尝胆,三千越甲可吞吴。
#include <algorithm>
#include <iostream>
#include <cstring>
#include <vector>
#include <cstdio>
#include <string>
#include <queue>
#include <cmath>
#include <ctime>
using namespace std;
typedef long long LL;
const int MAXN = 15001;
const int inf = (1<<29);
int n,P[MAXN],Tim,match[MAXN],vis[MAXN];
vector<int>medi[MAXN];
LL ans;

inline int getint(){
    int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar();
    if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w;
}

namespace network{
	const int MAXM = 1000011;
	int ecnt,first[MAXN],S,T,dui[MAXN],deep[MAXN];
	queue<int>q;
	struct edge{ int to,f,next; }e[MAXM];
	inline void Init(){ ecnt=1; S=n+1; T=S+1; }
	inline void link(int x,int y,int z){
		e[++ecnt].next=first[x]; first[x]=ecnt; e[ecnt].to=y; e[ecnt].f=z;
		e[++ecnt].next=first[y]; first[y]=ecnt; e[ecnt].to=x; e[ecnt].f=0;
	}

	inline bool bfs(){
		for(int i=1;i<=T;i++) deep[i]=-1; deep[S]=0; q.push(S); int u;
		while(!q.empty()){
			u=q.front(); q.pop();
			for(int i=first[u];i;i=e[i].next) {
				if(e[i].f==0) continue;
				int v=e[i].to; if(deep[v]!=-1) continue;
				deep[v]=deep[u]+1; q.push(v);
			}
		}
		return (deep[T]!=-1);
	}

	inline int dinic(int x,int remain){
		if(x==T || remain==0) return remain;
		int f,flow=0;
		for(int i=first[x];i;i=e[i].next) {
			if(e[i].f==0) continue;
			int v=e[i].to; if(deep[v]!=deep[x]+1) continue;
			f=dinic(v,min(remain,e[i].f));
			if(f==0) deep[v]=-1;
			else {
				flow+=f; remain-=f;
				e[i].f-=f; e[i^1].f+=f;
				if(remain==0) return flow;
			}
		}
		return flow;
	}
}

inline bool dfs(int x){
	if(vis[x]==Tim/*!!!*/) return false;//!!!
	vis[x]=Tim;
	for(int i=0,ss=medi[x].size();i<ss;i++) {
		int v=medi[x][i]; if(vis[v]==Tim) continue;
		if(!match[v] || dfs(match[v])) {
			match[v]=x;
			return true;
		}
	}
	return false;
}

inline void work(){
	using namespace network;
	n=getint(); Init(); int x,y;
	for(int i=1;i<=n;i++) { x=getint(); while(x--) { y=getint(); medi[i].push_back(y+n); } }
	for(int i=1;i<=n;i++) Tim++,dfs(i);
	for(int i=1;i<=n;i++) P[i]=getint();
	for(int i=1;i<=n;i++) for(int j=0,ss=medi[i].size();j<ss;j++) link(i,match[ medi[i][j] ],inf);
	for(int i=1;i<=n;i++) {
		P[i]=-P[i];
		if(P[i]>0) link(S,i,P[i]),ans+=P[i];
		else link(i,T,-P[i]);
	}

	while(bfs())
		ans-=dinic(S,inf);

	ans=-ans;
	printf("%I64d",ans);
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("103.in","r",stdin);
	freopen("103.out","w",stdout);
#endif
    work();
    return 0;
}
//有志者,事竟成,破釜沉舟,百二秦关终属楚;苦心人,天不负,卧薪尝胆,三千越甲可吞吴。
    

  

原文地址:https://www.cnblogs.com/ljh2000-jump/p/6670497.html