loj雅礼集训 2017 Day8」价(最大权闭合子图变形)

说实话真是个神仙题,日常被网络流草

推两篇我个人觉得讲的不错的文章

详细版本

简明版本

然后当然我自己也要来一遍啦~


一眼看去,像极了最大权闭合子图

等等,但是药材是不收费的啊…

而且药品的收益有正有负…

限制条件是药品数等于药材数


先说连边方案

没有权值我们就人为加上一个权值

下面定义 p [ i ] p[i] 为选择的收益(取反,因为负数的收益是正的)

s i p [ i ] + i n f s向药品i连流量p[i]+inf的边

i i n f 药品i向对应的药材连inf的边

t i n f 药材向t连inf的边

求最小割就可以解决问题(当然还不是答案)

. , \color{Red}Ⅰ.首先,最小割不会割药品和药材之间的边

很明显嘛,因为如果割了这种边,为什么不直接去割药材和 t t 之间的边呢?

割药材和 t t 的边等于是不选择这个药材,对最小割的贡献更大啊!!!

. , n \color{Red}Ⅱ.其次,最小割只会割n条边

由于只会割 s s 到药品的边,和药材到 t t 的边

因为 s s 到药品连了 n n 条边,药材到 t t 连了 n n 条边

而且不会割 n + 1 n+1 条边,因为权值都是 i n f inf 级别的,不划算

. ? \color{Red}Ⅲ.这个最小割有什么性质呢?

n , x ( ) n x ( ) 割掉了n条边,也就是割掉了x个药品(不选择)和n-x个药材(选择)

n x , n x , 也就是n-x个药品选择了,n-x个药材选择了,满足限制条件

, , 又因为是最大权闭合子图的求法,所以选择某个药品,相关的药材都会被选择

非常惊人的想法呢

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
const int inf=1e9;
int n,m,s,t;
int dis[maxn];
char a[109][109];
struct edge{
	int to,nxt,flow;
}d[maxn]; int head[maxn],cnt=1;
void add(int u,int v,int flow){
	d[++cnt]=(edge){v,head[u],flow},head[u]=cnt;
	d[++cnt]=(edge){u,head[v],0},head[v]=cnt;
}
bool bfs(int s,int t)
{
	memset(dis,0,sizeof(dis));
	dis[s]=1;
	queue<int>q; q.push( s );
	while( !q.empty() )
	{
		int u=q.front(); q.pop();
		for(int i=head[u];i;i=d[i].nxt )
		{
			int v=d[i].to;
			if( d[i].flow&&dis[v]==0 )
			{
				dis[v]=dis[u]+1;
				if( v==t )	return true;
				q.push( v );
			}
		}
	}
	return false;
}
int dinic(int u,int t,int flow)
{
	if( u==t )	return flow;
	int res=flow;
	for(int i=head[u];i&&res;i=d[i].nxt )
	{
		int v=d[i].to;
		if( dis[v]==dis[u]+1&&d[i].flow)
		{
			int temp=dinic(v,t,min(res,d[i].flow) );
			if( temp==0 )	dis[v]=0;
			res-=temp;
			d[i].flow-=temp;
			d[i^1].flow+=temp;
		}
	}
	return flow-res;
}
int main()
{
	cin >> n;
	s=0,t=n+n+1;
	for(int i=1;i<=n;i++)
	{
		int x; cin >> x;
		while( x-- )
		{
			int num; cin >> num;
			add(i,num+n,inf);
		}
	}
	ll sumn=(ll)inf*n,ans=0;
	for(int i=1;i<=n;i++)
	{
		int f; cin >> f;
		f=-f;//边权取反,变成正边权
		sumn+=f;
		add(s,i,f+inf);//源点连向药品 
		add(i+n,t,inf);//药材连向汇点 
	}
	while( bfs(s,t) )	ans+=dinic(s,t,inf);
	cout << -(sumn-ans);
}

猜你喜欢

转载自blog.csdn.net/jziwjxjd/article/details/108477909