小M的作物

luoguP1361 小M的作物

题目描述

小 M 在 MC 里开辟了两块巨大的耕地 \(A\) 和 \(B\)(你可以认为容量是无穷),现在,小 P 有 \(n\) 种作物的种子,每种作物的种子有 \(1\) 个(就是可以种一棵作物),编号为 \(1\) 到 \(n\)。

现在,第 \(i\) 种作物种植在 \(A\) 中种植可以获得 \(a_i\) 的收益,在 \(B\) 中种植可以获得 \(b_i\) 的收益,而且,现在还有这么一种神奇的现象,就是某些作物共同种在一块耕地中可以获得额外的收益,小 M 找到了规则*有 \(m\) 种作物组合,第 \(i\) 个组合中的作物共同种在 \(A\) 中可以获得 \(c_{1,i}\) 的额外收益,共同种在 \(B\) 中可以获得 \(c_{2,i}\) 的额外收益。

小 M 很快的算出了种植的最大收益,但是他想要考考你,你能回答他这个问题么?

解法:

这是一道非常经典的二者取一类的最小割的题目。

对任意一种作物,我们让源点向他的连边的容量为在 \(A\) 中种植的收益,他向汇点的连边的容量为在 \(B\) 中种植的收益(反过来也可以)。

在这类题中,割掉的边意味着不选,留下的边是这个节点的选择,比如如果留下了这个点到汇点的连边,那么这就意味着这种作物不在 \(A\) 中种,在 \(B\) 中种。

接下来考虑如何统计同在 \(A\) 中种植的收益和同在 \(B\) 中种植的收益。因为这两种本质相同,我们就只讨论同在 \(A\) 中种植的收益。我们对新建一个节点表示这种方案,源点向这个点的连边的容量就是存在这种方案的价值,这个点再向所有包括在这种方案在内的节点连一条权值为 \(INF\) 的边。

在最小割中如果有权值为 \(INF\) 的边,就意味着这条边不可割。

为什么这样子是对的呢?对任意一个在这个方案中的节点,如果他割掉的是与源点的连边,留下的是与汇点的连边,那么这个方案所代表的节点与源点的连边也要割掉,不然就存在一条 "源点—方案节点—当前节点—汇点" 这样一条路径使源点可以到达汇点,显然这是不行的,而且我们已经割掉了和源点的连边,意味着我们 必须 要留下和汇点的连边,而方案节点和当前节点的连边权值是 \(INF\),是一条不可割边,所以我们只能把 "源点—方案节点" 这条边给割掉。

同理,同在 \(B\) 中种植的作物就是将作物代表的节点连线当前代表这个方案的节点,权值为 \(INF\);这个方案的节点连向汇点,权值是这种方案的价值。

最后求最小割就可以了。

Code:

#include <iostream>
#include <cstring>
#include <queue>
using namespace std ;

const int INF = 0x3f3f3f3f ;
int n , m , s , t , tot , dis[20005] , ans = 0 ;

struct Edge
{
	int nxt , to , len ;
} edge[4000005] ;

int cnt = 1 , head[20005] ;
void insert ( int u , int v , int w )
{
	edge [ ++ cnt ] .nxt = head [ u ] ;
	edge [ cnt ] .to = v ;
	edge [ cnt ] .len = w ;
	head [ u ] = cnt ;
}

queue < int > q ;
bool bfs ( )
{
	memset ( dis , 0 , sizeof ( dis ) ) ;
	dis [ s ] = 1 ;
	q .push ( s ) ;
	while ( ! q .empty ( ) )
	{
		int x = q .front ( ) ; q .pop ( ) ;
		for ( int i = head [ x ] ; i ; i = edge [ i ] .nxt )
		{
			int y = edge [ i ] .to ;
			if ( dis [ y ] || ! edge [ i ] .len )
				continue ;
			dis [ y ] = dis [ x ] + 1 ;
			q .push ( y ) ;
		}
	}
	return dis [ t ] ;
}

int dfs ( int x , int now )
{
	if ( x == t )
		return now ;
	int res = now ;
	for ( int i = head [ x ] ; i && res ; i = edge [ i ] .nxt )
	{
		int y = edge [ i ] .to ;
		if ( dis [ y ] != dis [ x ] + 1 || ! edge [ i ] .len )
			continue ;
		int w = dfs ( y , min ( res , edge [ i ] .len ) ) ;
		if ( ! w ) dis [ y ] = 0 ;
		edge [ i ] .len -= w ;
		edge [ i ^ 1 ] .len += w ;
		res -= w ;
	}
	return now - res ;
}

int main ( )
{
	cin >> n ; tot = n ;
	s = ++ tot ;
	t = ++ tot ;
	for ( int i = 1 ; i <= n ; ++ i )
	{
		int x ;
		cin >> x ;
		insert ( s , i , x ) ;
		insert ( i , s , 0 ) ;
		ans += x ;
	}
	for ( int i = 1 ; i <= n ; ++ i )
	{
		int x ;
		cin >> x ;
		insert ( i , t , x ) ;
		insert ( t , i , 0 ) ;
		ans += x ;
	}
	cin >> m ;
	for ( int i = 1 ; i <= m ; ++ i )
	{
		int k , c1 , c2 , si , ti ;
		cin >> k >> c1 >> c2 ;
		ans += c1 + c2 ;
		si = ++ tot ;
		ti = ++ tot ;
		insert ( s , si , c1 ) ;
		insert ( si , s , 0 ) ;
		insert ( ti , t , c2 ) ;
		insert ( t , ti , 0 ) ;
		for ( int j = 1 ; j <= k ; ++ j )
		{
			int x ;
			cin >> x ;
			insert ( si , x , INF ) ;
			insert ( x , si , 0 ) ;
			insert ( x , ti , INF ) ;
			insert ( ti , x , 0 ) ;
		}
	}
	int tmp = 0 ;
	while ( bfs ( ) )
		while ( tmp = dfs ( s , INF ) )
			ans -= tmp ;
	cout << ans << '\n' ;
	return 0 ;
}
上一篇:[网络流24题]P2770 航空路线


下一篇:【Luogu P4568】[JLOI2011]飞行路线