子矩阵 解题报告

子矩阵 解题报告

题目大意

在一个N行M列的矩阵中,选出一个R行C列的子矩阵,使相邻元素的差的绝对值的和最小。

解题方法

1. 暴力出奇迹

解题思路

在一个N行M列的矩阵中,选出一个 \(R\)\(C\) 列的子矩阵,相当于一个 \(N\)\(M\) 列的矩阵,在 \(N\) 行中选出 \(R\) 行,在 \(M\) 列中选出 \(C\) 列,选出的行和列相交的点就是选出的数。
所以一个很简单的办法,就是枚举 \(N\) 行中选哪 \(R\) 行, \(M\) 列中选哪 \(C\) 列。这就是一个组合问题,相当于枚举 \(R \choose N\)\(C \choose M\)
这个算法的时间复杂度,就是枚举的 \(R \choose M\) \(\times\) \(C \choose M\) 在乘上计算的 \(R \times C\) ,刚好可以得60分。

代码大致是这样实现的:

  1. 枚举 \(R \choose M\)
  2. 枚举 \(R \choose M\)
  3. 在第二步枚举完后求值,更新答案

枚举组合,可以用DFS来实现:

int n, r, a [ MaxN + 1 ];
void solve ( int step )
{
	
	if ( step > r )
	{
		print ( );
		return ;
	}
	
	for ( int i = a [ step - 1 ] + 1; i <= n; i ++ ) a [ step ] = i, solve ( step + 1 );
	
}

你可以拿这个代码去实现一些题目(当然不行),你会发现,在 \(N<=15\) 的时候大致可以过(主要看评测机是赛扬还是i99900k),但是你可以试一试这道题,TLE的很厉害。

仔细观察i的范围,这里显然可以做一些优化。
"\(i <= n\)" 这个范围很不合理,如果剩余空间不够 \(r - step\) 个,肯定不可能。
因此,\(n - i\) 必须 \(>= r - step\),转化得到 \(i <= n - r + step\)

代码只用修改一处,就是把 for ( int i = a [ step - 1 ] + 1; i <= n; i ++ ) 变为 for ( int i = a [ step - 1 ] + 1; i <= n - r + step; i ++ )

代码1

回到这道题目上,代码实现很简单:

//主程序请自行实现

void dfs1 ( int step )
{
	
	if ( step > r ) { dfs2 ( 1 ); return ; }
	
	for ( int i = row [ step - 1 ] + 1; i <= n - r + step; i ++ ) row [ step ] = i,dfs1 ( step + 1 );
	
}

void dfs2 ( int step )
{
	
	if ( step > c )
	{
		
		int sum = 0;
		
		//统计分数部分请自行实现
		
		if ( sum < ans ) ans = sum;

                return ;
		
	}
	
	for ( int i = col [ step - 1 ] + 1; i <= m - c + step; i ++ ) col [ step ] = i,dfs2 ( step + 1 );
	
}

2. 动态规划

如果用DP,可以 参考其他题目 这样写:
\(f [ n ] [ r ] [ m ] [ c ]\) 表示 \(n\) 列中取 \(r\) 列,\(m\) 列中取 \(c\) 列,第 \(n\) 行和第 \(m\) 列必须取时的最小分值。

但这样写会出现状态不明确的问题,也就是有后效性。
不能全部用DP,那咋办???

别急,看后面 》》》

3. 一半DFS一半DP

解题思路

既然全部DP或者记忆化状态会不明确,那么通过再枚举就可以确定状态了(这里选择列)。

\(f [ i ] [ j ]\) 表示 \(i\) 列中取 \(j\) 列,第 \(i\) 列必需取的最小分值

\(f [ i ] [ j ] = \min_{ f [ k ] [ j - 1 ] + \sum_{ x = 1 } ^ { r - 1 } { | a [ row [ x ] ] [ i ] - a [ row [ x - 1 ] ] [ i ] | } + \sum_{ x = 1 } ^ { r } { | abs ( a [ row [ x ] ] [ i ] - a [ row [ x ] ] [ j ] | } }\)
(\(2 \leq i \leq m, 2 \leq j \leq min ( i, c ), j - 1 \leq k < i\))

这样写出的DP大致是这么一坨东西:

/*
请先预处理:
col [ i ] 表示i列内部的分值(相当于第一个sigma)
cc [ i ] [ j ] 表示第i列与第j列相邻时,他们列之间相差的分值(相当于第二个sigma)
*/
	
for ( int i = 2; i <= m; i ++ )
	for ( int j = 2; j <= min ( i, c ); j ++ )
		for ( int k = j - 1; k < i; k ++ )
			f [ i ] [ j ] = min ( f [ i ] [ j ], f [ k ] [ j - 1 ] + col [ i ] + cc [ k ] [ i ] );

再结合一下之前的DFS,写出代码:

代码2

//同样,主程序请自行实现
//码风小清新,多多照顾
//适当做了一些优化(630ms)

void dfs ( int x )
{

	if ( x > r )
	{

		for ( int i = 1; i <= m; i ++ )
		{

			for ( int j = 1; j <= m; j ++ )
			{
				
				cc [ i ] [ j ] = 0;

				for ( int k = 1; k <= r; k ++ )
				{
					cc [ i ] [ j ] += abs ( a [ row [ k ] ] [ i ] - a [ row [ k ] ] [ j ] );
				}

			}

		}

		for ( int i = 1; i <= m; i ++ )
		{
			
			col [ i ] = 0;

			for ( int j = 1; j < r; j ++ )
			{
				col [ i ] += abs ( a [ row [ j ] ] [ i ] - a [ row [ j + 1 ] ] [ i ] );
			}

		}

		for ( int i = 1; i <= m; i ++ )
		{
			f [ i ] [ 1 ] = col [ i ];
		}

		for ( int i = 2; i <= m; i ++ )
		{

			for ( int j = 2; j <= min ( i, c ); j ++ )
			{
				
				f [ i ] [ j ] = 0x3f3f3f3f

				for ( int k = j - 1; k < i; k ++ )
				{
					f [ i ] [ j ] = min ( f [ i ] [ j ], f [ k ] [ j - 1 ] + col [ i ] + cc [ k ] [ i ] );
				}

			}

		}

		for ( int i = c; i <= m; i ++ )
		{
			res = min ( f [ i ] [ c ], res );
		}

		return;

	}

	for ( int i = row [ x - 1 ] + 1; i <= n - r + x; i ++ )
	{

		row [ x ] = i;

		dfs ( x + 1 );

	}

}

总结时间到!

诶,这道题主要是一个DFS和DP结合的玩意。
当发现DFS转DP会产生后效性时,有两种方法:

  1. 加状态
  2. 再搜索

第一种方法显然不现实 十六维数组,为啥不给把16G内存全用上呢?(手滑=手动滑稽
那就只能用第二种方法,就产生了这一坨DFS+DP的东东。

解这种烦人的村民题时,一般先打一个暴力(NOIP2014,如果前三题AC,一等奖稳稳的),再去优化成DP OR 记忆化。

哎吗,这篇题解写了我好几年哟,赞一个呗

猜你喜欢

转载自www.cnblogs.com/wukaixuan/p/12952036.html