蜥蜴


luoguP2472 [SCOI2007]蜥蜴

题目描述

题目链接

在一个 \(r\)\(c\) 列的网格地图中有一些高度不同的石柱,第 \(i\)\(j\) 列的石柱高度为 \(h_{i,j}\)

一些石柱上站着一些蜥蜴,你的任务是让尽量多的蜥蜴逃到边界外。

每行每列中相邻石柱的距离为 \(1\),蜥蜴的跳跃距离是 \(d\),即蜥蜴可以跳到平面距离不超过 \(d\) 的任何一个石柱上。

石柱都不稳定,每次当蜥蜴跳跃时,所离开的石柱高度减 \(1\)(如果仍然落在地图内部,则到达的石柱高度不变)。

如果该石柱原来高度为 \(1\),则蜥蜴离开后消失,以后其他蜥蜴不能落脚。

任何时刻不能有两只蜥蜴在同一个石柱上。

数据范围:1\(\le r,c\le20\)\(1\le d\le 4\)\(1\le h\le 3\)

解法

首先,观察数据范围,看到不仅 \(h\)\(d\) 很小,而且 \(r\)\(c\) 也很小,所以我们在这里可以用到某些复杂度比较大算法,而且其中的 \(h\)\(d\) 甚至可以当成常数。

然后稍微转化一下题意,要求无法逃离的蜥蜴的最小值,显然就是要求能够逃离的蜥蜴的最大值。

在确定了上面两条做题的小技巧的时候,我们就应该能差不多想到要用最大流了。

考虑建图,先考虑如果说没有高度会降低的限制的话。

对任意一个有蜥蜴的石柱,我们把它连到原点上,边的容量为 \(1\);然后对于任意一个能够 从当前位置跳出去\(^{[1]}\) 的且有高度的石柱,我们把它连到汇点上,边的容量为 \(INF\);之后再将每个能够 相互到达\(^{[2]}\) 的石柱所对应的节点相互建边,边容量为 \(INF\)

接下来考虑如果高度会降低,那么显然一个石柱能够承载的蜥蜴的次数是有限制的,在网络流的模型里就是流过这个点的流量是有限制的,考虑如何将这个限制加入建图。

一种比较经典的点容量转边容量的方法就是拆点,将这个点拆成一个入点和一个出点,入点向出点连容量等于这个点的高度的边,将所有连向这个点的边连向入点,出点连向这个点连向的点。

最后跑最大流就行了。

\([1]\):对于判断这个能否跳出去,这里给出两种方法,一种是机房同学做的,枚举图上矩阵每一个点,判断横纵坐标加减 \(d\) 是否有跳出界外的。

另一种是我做的,首先我们发现所有能跳出界外的只会是最外面的一圈,且宽度为 \(d\) 的一个空心矩形,我们把这个矩形分成四部分,形成类似于风车的形状。

2

位于 \(1\) 矩形的横坐标范围是 \([1,c-d]\),纵坐标是 \([1,d]\)

位于 \(2\) 矩形的横坐标范围是 \([c-d+1,c]\),纵坐标是 \([1,r-d]\)

位于 \(3\) 矩形的横坐标范围是 \([d+1,c]\),纵坐标是 \([r-d+1,r]\)

位于 \(4\) 矩形的横坐标范围是 \([1,d]\),纵坐标是 \([d+1,r]\)

\([2]\):对任意两个石柱能否互相到达,这里也给出两种方法,一种是枚举每一个点,再暴力枚举一遍图上的每一个点,看这两个枚举的点是否超过了距离,但是这样这里的建边就会成为整个题的复杂度瓶颈 \(O(n^4)\)

第二种是我做的,如果先对每个点进行编号,先由左到右,再由上到下依次编号,我们发现对每个点 \((x,y)\) 只要寻找每个点之后的就可以了,然后连上两条双向边。对于判断是否再范围内,如果是同一行就直接连后面的 \(d\) 个,如果不同行就枚举横坐标在 \((x-d,x+d)\) 范围内,纵坐标在 \([y+1,y+d]\) 范围内的即可,枚举量相对于上面那种方法应该是减小了不少。

Code

#include 
#include 
#include 
#include 
using namespace std ;

const int INF = 0x3f3f3f3f ;
int n , m , d , a[25][25] , s , t , dis[805] , ans ;
string ci ;

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

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

int id ( int i , int j , int op )
{
	return ( i - 1 ) * m + j + op * n * m ;
}

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 = cur [ x ] ; i && res ; i = edge [ i ] .nxt )
	{
		int y = edge [ i ] .to ;
		cur [ x ] = edge [ i ] .nxt ;
		if ( dis [ y ] != dis [ x ] + 1 || ! edge [ i ] .len )
			continue ;
		int w = dfs ( y , min ( res , edge [ i ] .len ) ) ;
		if ( ! w ) dis [ y ] = -1 ;
		edge [ i ] .len -= w ;
		edge [ i ^ 1 ] .len += w ;
		res -= w ;
	}
	return now - res ;
}

int main ( )
{
	cin >> n >> m >> d ;
	s = n * m * 2 + 1 ;
	t = s + 1 ;
	for ( int i = 1 ; i <= n ; ++ i )
	{
		cin >> ci ;
		for ( int j = 0 ; j < m ; ++ j )
		{
			a [ i ] [ j + 1 ] = ci [ j ] - '0' ;
			if ( a [ i ] [ j + 1 ] )
				insert ( id ( i , j + 1 , 0 ) , id ( i , j + 1 , 1 ) , a [ i ] [ j + 1 ] ) ,
				insert ( id ( i , j + 1 , 1 ) , id ( i , j + 1 , 0 ) , 0 ) ;
		}
	}
	for ( int i = 1 ; i <= n ; ++ i )
	{
		cin >> ci ;
		for ( int j = 0 ; j < m ; ++ j )
		{
			if ( ci [ j ] == 'L' )
				insert ( s , id ( i , j + 1 , 0 ) , 1 ) ,
				insert ( id ( i , j + 1 , 0 ) , s , 0 ) , ++ ans ;
		}
	}
	if ( n <= d * 2 || m <= d * 2 )
	{
		cout << ans << '\n' ;
		return 0 ;
	}
	for ( int i = 1 ; i <= d ; ++ i )
	{
		for ( int j = 1 ; j <= m - d ; ++ j )
			if ( a [ i ] [ j ] )
				insert ( id ( i , j , 1 ) , t , INF ) ,
				insert ( t , id ( i , j , 1 ) , 0 ) ;
		for ( int j = 1 ; j <= n - d ; ++ j )
			if ( a [ j ] [ m - i + 1 ] )
				insert ( id ( j , m - i + 1 , 1 ) , t , INF ) ,
				insert ( t , id ( j , m - i + 1 , 1 ) , 0 ) ;
		for ( int j = d + 1 ; j <= m ; ++ j )
			if ( a [ n - i + 1 ] [ j ] )
				insert ( id ( n - i + 1 , j , 1 ) , t , INF ) ,
				insert ( t , id ( n - i + 1 , j , 1 ) , 0 ) ;
		for ( int j = d + 1 ; j <= n ; ++ j )
			if ( a [ j ] [ i ] )
				insert ( id ( j , i , 1 ) , t , INF ) ,
				insert ( t , id ( j , i , 1 ) , 0 ) ;
	}
	for ( int i = 1 ; i <= n ; ++ i )
		for ( int j = 1 ; j <= m ; ++ j )
		{
			if ( ! a [ i ] [ j ] )
				continue ;
			for ( int c = j + 1 ; c <= min ( j + d , m ) ; ++ c )
			{
				if ( ! a [ i ] [ c ] )
					continue ;
				insert ( id ( i , j , 1 ) , id ( i , c , 0 ) , INF ) ;
				insert ( id ( i , c , 0 ) , id ( i , j , 1 ) , 0 ) ;
				insert ( id ( i , c , 1 ) , id ( i , j , 0 ) , INF ) ;
				insert ( id ( i , j , 0 ) , id ( i , c , 1 ) , 0 ) ;
			}
			for ( int r = i + 1 ; r <= min ( i + d , n ) ; ++ r )
				for ( int c = max ( 1 , j - d ) ; c <= min ( j + d , m ) ; ++ c )
				{
					if ( ! a [ r ] [ c ] ) continue ;
					if ( ( r - i ) * ( r - i ) + ( c - j ) * ( c - j ) <= d * d )
					{
						insert ( id ( i , j , 1 ) , id ( r , c , 0 ) , INF ) ;
						insert ( id ( r , c , 0 ) , id ( i , j , 1 ) , 0 ) ;
						insert ( id ( r , c , 1 ) , id ( i , j , 0 ) , INF ) ;
						insert ( id ( i , j , 0 ) , id ( r , c , 1 ) , 0 ) ;
					}
				}
		}
	int tmp = 0 ;
	while ( bfs ( ) )
	{
		for ( int i = 1 ; i <= n * m * 2 + 2 ; ++ i )
			cur [ i ] = head [ i ] ;
		while ( tmp = dfs ( s , INF ) )
			ans -= tmp ;
	}
	cout << ans << '\n' ;
	return 0 ;
}