UVa Problem 861 Little Bishops (棋盘上的象)

// Little Bishops (棋盘上的象)
// PC/UVa IDs: 110801/861, Popularity: C, Success rate: high Level: 2
// Verdict: Accepted 
// Submission Date: 2011-06-19
// UVa Run Time: 0.024s
//
// 版权所有(C)2011,邱秋。metaphysis # yeah dot net
//
// n * n 的棋盘可以最多放置多少个象而不互相冲突?我是这样考虑的,因为象的吃子方式为
// 对角线吃子,则在棋盘上放置一个象后,它要么占据一条对角线,要么占据两条。当然先放置
// 占据一条对角线的象,可以将放置象的数量最大化,在棋盘的主、副主对角线上各放置一个
// 象,则象的总数目为 2,这样,主、副对角线将棋盘区域划分为四个等同的区域,无论在哪
// 个区域放置象,都因为棋盘的对称性会产生相同的效果,故只考虑一个区域的放置方法,然后
// 通过对称来求得总的放置数目。假设一个 8 * 8 的棋盘,已经在主、副对角线各放置了一个
// 象,已占据的位置用 * 号表示,未占据的位置用大写字母 O 表示。
//
//   1 2 3 4 5 6 7 8
// 1 B O-O-O-O-O-O B
// 2 O * O-O-O-O * O
// 3 O#O * O-O * O&O
// 4 O#O#O * * O&O&O
// 5 O#O#O * * O&O&O
// 6 O#O * O+O * O&O
// 7 O * O+O+O+O * O
// 8 * O+O+O+O+O+O *
//
// 如上图所示,有 - 符号连接的未占据位置和 + 符号连接的未占据位置,坐标 (1, 1) 和
// 坐标 (8, 1) 已经放置了象。先考虑 - 符号连接的区域,在此区域内,任意选一个位置放置
// 象将占据两条对角线(非主、副对角线),而且在此区域任意放置象,必将导致左右两个可以
// 放置象的区域被占据而不能再放置象(即有 # 符号和 & 符号的区域),则在有 - 符号的区
// 域所能放置的象的数目是由对角线的交叉点数目所确定的,即最多能再放置 6 个象而不互相
// 冲突,则总共能放置的象数目为 8 个,棋盘状态变为以下状态:
//
//   1 2 3 4 5 6 7 8
// 1 B B B B B B B B
// 2 * * * * * * * *
// 3 * * * * * * * *
// 4 * * * * * * * *
// 5 * * * * * * * *
// 6 * * * O+O * * *
// 7 * * O+O+O+O * *
// 8 * O+O+O+O+O+O *
//
// 则对于有 + 符号的区域来说,同样可以放置 6 个象而不互相冲突。则 8 * 8 的棋盘最大
// 可放置象的数目为 14 个。
//
//   1 2 3 4 5 6 7 8
// 1 B B B B B B B B
// 2 * * * * * * * *
// 3 * * * * * * * *
// 4 * * * * * * * *
// 5 * * * * * * * *
// 6 * * * * * * * *
// 7 * * * * * * * *
// 8 * B B B B B B *
//
// 则总结以下,n * n 的棋盘最大能放置象的数目为 n + (n - 2) 个,即 2 * (n - 1)
// 个象,n >= 2。对于 1 * 1 的棋盘来说,是特殊情况,只能放置 1 个象。那么是否可以通
// 过组合的方法解决本题呢?答案是肯定的。国际象棋的棋盘一般都分为白色和黑色区域,在白色
// 区域的象是无法攻击黑色区域内的象的,将黑白格子相间的棋盘顺时针旋转 45 度,则原来呈
// 斜线的主、副对角线成为垂直和水平的了,此时象的走法和车的走法是一样的了,问题转换为在
// 这样的 n * n 棋盘上放置 k 个车有多少种方法。假设这样的棋盘第 i 行的格子数为 r[i],
// 用 t[i][j] 表示在前 i 行放置 j 个车而互不冲突的方法,可以得到以下的递推关系:
//
// t[i][j] = t[i - 1][j] + t[i - 1][j - 1] * (r[i] - (j - 1))
//
// 边界条件是:
//
// t[i][0] = 1, 0 <= i <= n
// t[0][j] = 0, 1 <= j <= k
//
// 递推关系的意义可以这样理解:因为每一行只能放置一个车,则 j 个车要么全在前 i - 1 行,
// 要么第 i 行有一个车,j 个车全在前 i - 1 行的放置方法为 t[i - 1][j],第 i 行放置
// 一个车,前 i - 1 行放置 j - 1 个车,那么前 i - 1 行在放置 j - 1 个车时已经占用
// 了第 i 行的 j - 1 个格子,剩余的格子数为 r[i] - (j - 1),则根据乘法原理,第二种
// 放置方法是两者的乘积,又根据加法原理,总的放置方法为第一种和第二种方法数量的和。边界
// 情形也容易理解,前 i 行放置 0 个车的方法有 1 种,前 0 行放置 j 个车的方法有 0 种。
// 由于将棋盘分成了两个区域,故在最后计算总的放置数时,应该是两个区域的累积。
//
// 那么如何通过借鉴八皇后问题通过回溯来解决本问题呢?通过观察分析,可以知道,构造候选集
// 的方法是其中关键不同的地方,八皇后问题在构建候选集时,因为皇后不能放置在同一行和同一
// 列,所以可以减少搜索的空间,而放置象时,象可以在同一行或者同一列,搜索的数量因此会增大
// 不过当棋盘较少时,还是可以完成的,当棋盘进一步增大时,回溯方法就显得吃力了,需要借助
// 组合数学的方法来计算放置方案数。在表示棋盘上的象的位置时,由于可以处于同一行或同一列
// 故需要不同的表示方法,一个方法是将棋盘的每个格子编号,从 1 - n^2。
//
// /* 八皇后问题构建候选集的过程。  */
// construct_candidates(int a[], int k, int n, int c[], int *ncandidates)
// {
//      int i,j;
//      bool legal_move;
//      
//      *ncandidates = 0;
//      for (i=1; i<=n; i++)
//      {
//              legal_move = TRUE;
//              /* 对于放置象来说,需要考虑除已有象的对角线外的每一个位置,因为不存 */
//              /* 在像放置皇后时一行或一列只能放置一个这样的限制条件。 */
//              for (j=1; j<k; j++)
//              {
//                      if (abs((k)-j) == abs(i-a[j])) 
//                      legal_move = FALSE;
//                      
//                      /* 对于放置象来说,并不需要检测来自行或者列的威胁,只需检 */
//                      /* 测对角线上的威胁。*/
//                      if (i == a[j])                  
//                      legal_move = FALSE;
//              }
//              
//              if (legal_move == TRUE) 
//              {
//                      c[*ncandidates] = i;
//                      *ncandidates = *ncandidates + 1;
//              }
//      }
// }
//
// 对于构建候选集的过程,尽管不需要考虑来自行或者列的威胁,但需要考虑除对角线外的每一个
// 位置,且并不存在一行只能放一个象的限制条件,这是搜索时间增加的原因。如果在放置象时,
// 不考虑位置的编号,会产生重复的放置方案,这个可以通过每次选择象时都选择比当前已选择的
// 象的位置序号大的位置来避免。尽管采用了相应的剪枝措施,对于较大的 n 和 k 来说,仍容易
// 得到 TLE。相对而言通过组合方法解题还是有优势的,除非用回溯法先生成给定范围内的所
// 有解,然后填表根据具体的 n 和 k 输出,否则当 n 和 k 进一步增大,计算时间将很长。
//
// UVa 10237 Bishops 和本题是类似的,但是 n 和 k 已经足够大,通过回溯已经不可能在
// 规定时间内找到答案,使用组合方法和大数运算成为必须。
	
#include <iostream>
#include <algorithm>
	
using namespace std;
	
#define MAXN 8
	
long long solution_count;
	
void construct_candidates(int bishops[], int c, int n, int candidates[], 
	int *ncandidates)
{
	bool legal_move;	// 合法放置位置的标记。
	
	// 对于放置象来说,需要考虑每一个位置,因为不存在像放置皇后时一行只能放置
	// 一个的情况。只考虑比当前象的位置标记大的位置,避免重复解的生成。因为保证
	// 了后一位置的象的编号大于前一位置象的编号,故可以从具有最大编号的象的位置
	// 开始搜索可放置象的位置,这样可以减少搜索量。
	int start = 0;
	if (c)
		start = bishops[c - 1];
	
	*ncandidates = 0;
	for (int p = start; p < n * n; p++)
	{
		legal_move = true;
	
		// 已放置象的对角线上不能放置。需要检查已放置象的对角线。
		// 不满足条件,尽早退出循环。
		for (int j = 0; j < c; j++)
			if (abs(bishops[j] / n - p / n) ==
				abs(bishops[j] % n - p % n))
			{
				legal_move = false;
				break;
			}
	
		// 若该位置合法,添加到候选集中。
		if (legal_move == true)
			candidates[(*ncandidates)++] = p;
	}
}
	
// 回溯寻找所有可能的方案。
void backtracking(int bishops[], int c, int n, int k)
{
	if (c == k)
		solution_count++;
	else
	{
		int ncandidates;
		int candidates[MAXN * MAXN];
			
		// 构建候选集。
		construct_candidates(bishops, c, n, candidates, &ncandidates);

		for (int i = 0; i < ncandidates; i++)
		{
			bishops[c] = candidates[i];
			backtracking(bishops, c + 1, n, k);
		}
	}
}

long long little_bishops_by_backtracking(int n, int k)
{
	int bishops[2 * (MAXN - 1) + 1];
	
	solution_count = 0;
	backtracking(bishops, 0, n, k);
	
	return solution_count;
}

long long little_bishops_by_combinatorics(int n, int k)
{
	// 假设棋盘左上角第一个格子为白色格子。
	long long white[9];
	long long black[9];
	
	// 得到每一列白色格子的数目。格子数按从小到大排列。
	for (int i = 1; i <= n; i++)
		white[i] = ((i % 2) ? i : white[i - 1]);
	// 得到每一列黑色格子的数目。格子数按从小到大排列。
	for (int i = 1; i <= n - 1; i++)
		black[i] = ((i % 2) ? (i + 1) : black[i - 1]);
	
	// 存储前 i 列放置 j 个象的方法。白色格子和黑色格子的放置方法数分别计算。
	// 因为给定最多 8 * 8 的棋盘,则最大能放置象的数目为 14 个。	
	long long white_solutions[9][15] = { {0} };
	long long black_solutions[9][15] = { {0} };
	// 初始化边界条件。
	for (int i = 0; i <= n; i++)
		white_solutions[i][0] = 1;
	for (int j = 1; j <= k; j++)
		white_solutions[0][j] = 0;
	// 根据递推公式计算白色格子放置方案数。
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= k && j <= i; j++)
			white_solutions[i][j] =
				white_solutions[i - 1][j] + 
				white_solutions[i - 1][j - 1] * (white[i] - j + 1);
	// 初始化边界条件。
	for (int i = 0; i <= n - 1; i++)
		black_solutions[i][0] = 1;
	for (int j = 1; j <= k; j++)
		black_solutions[0][j] = 0;
	// 根据递推公式计算黑色格子放置方案数。
	for (int i = 1; i <= n - 1; i++)
		for (int j = 1; j <= k && j <= i; j++)
			black_solutions[i][j] =
				black_solutions[i - 1][j] + 
				black_solutions[i - 1][j - 1] * (black[i] - j + 1);
	
	// 统计总的放置方案数。根据乘法原理和加法原理,总的方案数等于 n * n 的棋盘
	// n 行白色格子放置 0 个象的方案乘以 n - 1 行黑色格子放置 k 个象的方案数,
	// 加上 n 行白色格子放置 1 个象的方案乘以 n - 1 行黑色格子放置 k - 1 个
	// 象的方案数,加上 n 行白色格子放置 2 个象的方案乘以 n - 1 行黑色格子放
	// 置 k - 2 个象的方案数......
	long long total = 0;
	for (int i = 0; i <= k; i++)
		total += white_solutions[n][i] * black_solutions[n - 1][k - i];
	return total;
}
	
long long little_bishops(int n, int k)
{
	// 处理特殊情况的解。
	// k == 0,即棋盘上不放置象,只有一种方法。
	if (k == 0)
		return 1LL;
	
	// 当棋盘为 1 * 1 时,最多只能放置放置 1 个象。
	if (n == 1)
		return k;
	
	// 当 n >= 2 时,由分析可知,最多只能放置 2 * (n - 1) 个象。
	// 当大于 2 * (n - 1) 时无解。
	if (k > 2 * (n - 1))
		return 0LL;
	
	// 一般情况的解。
	//return little_bishops_by_backtracking(n, k);
	return little_bishops_by_combinatorics(n, k);
}
	
int main(int ac, char *av[])
{
	int n, k;
	
	while (cin >> n >> k, n || k)
		cout << little_bishops(n, k) << endl;
	
	return 0;
}


  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值