微软编程之美大赛-长方形个数问题

本文是关于2013年微软编程之美大赛的一个问题,探讨如何在N条水平线与M条竖直线构成的网格中,放置K枚石子,以形成最多的四边平行于坐标轴的长方形。文章详细介绍了问题描述、限制条件、输入输出要求,以及作者的解题思路。通过计算最优策略,找到可能的最大长方形数量。

时间:2014.04.05

地点:基地二楼

---------------------------------------------------------------------

一、简述

  这是2013年微软编程之美大赛资格赛的题目。

---------------------------------------------------------------------

时间限制: 1000ms 内存限制: 256MB

2.题目

在 N 条水平线与 M 条竖直线构成的网格中,放 K 枚石子,每个石子都只能放在网格的交叉点上。问在最优的摆放方式下,最多能找到多少四边平行于坐标轴的长方形,它的四个角上都恰好放着一枚石子。

3.要求

输入文件包含多组测试数据。

第一行,给出一个整数T,为数据组数。接下来依次给出每组测试数据。

每组数据为三个用空格隔开的整数 N,M,K。

4.输出

对于每组测试数据,输出一行"Case #X: Y",其中X表示测试数据编号,Y表示最多能找到的符合条件的长方形数量。所有数据按读入顺序从1开始编号。

5.数据范围

1 ≤ T ≤ 100

0 ≤ K ≤ N * M

小数据:0 < N, M ≤ 30

大数据:0 < N, M ≤ 30000

6.样例输入

3
3 3 8
4 5 13
7 14 86

7.样例输出

Case #1: 5
Case #2: 18
Case #3: 1398

---------------------------------------------------------------------

三、思路

  我的想法是,先对给定的点数K开根号,因为我们有限制K<=N*M,所以K在网格中总是放得下的,将K开根号后的值与N、M中的最小值做比较,如果根号值小,而当点形成一个方正时总是能获得最多矩形数,所以我们尽量向方正靠拢,于是可先计算所得方正形成的矩形数,然后将剩余的点依次往方正后面补,计算第二部分新形成的矩形数。还有一种情况是当根号值大于或等于最小边长时,这时我们不能按根号值形成方正,但可按最小边长形成一个矩形,第一部分先计算这个矩形中的矩形数,第二部分和上面一样,将余下的点数往矩形后面追加,并计算新的矩形数即可。代码如下:

#include<iostream>
#include<vector>
#include<cassert>
using namespace std;
unsigned CalculateGrid(unsigned N, unsigned M, unsigned K);
//Preconditon: N,M fail into [0,30 000] and K fail into [0,N*M]
//Postcondition: Return the max matrix numbers whitch every matrix's edge paralell to grid
bool CheckTheInput(unsigned N, unsigned M, unsigned K);
//Precondition: Check the parameters' leagal
//Postcondition: Return true when every parameter is leagal
void PrintTheResult(vector<unsigned>& vec);
//Postcondition: Print this test's result according to the requirements
unsigned CalculateMartrix(unsigned row, unsigned col);
//Precondition: Given a matrix
//Postcondition:Return this matrix's sub_matrixs number
int main()
{
	unsigned T = 0;
	cin >> T;
	assert(T >= 1 && T <= 100);

	unsigned N, M, K;
	vector<unsigned> matrix_count;
	while (T)
	{
		cin >> N>>M>>K;
		assert(CheckTheInput(N, M, K));
		matrix_count.push_back(CalculateGrid(N, M, K));
		--T;
	}
	PrintTheResult(matrix_count);
	return 0;
}
unsigned CalculateMartrix(unsigned row, unsigned col)
{
	return row*col*(row - 1)*(col - 1) / 4;
}
unsigned CalculateGrid(unsigned N, unsigned M, unsigned K)
{
	unsigned min_edge = N < M ? N : M;
	unsigned squre_root = static_cast<unsigned>(sqrt(K));
	unsigned first_part, second_part, difference;
	if (squre_root<min_edge)
	{
		first_part = CalculateMartrix(squre_root,squre_root);
		difference = K - squre_root*squre_root;
		if (difference > 1 && difference <= squre_root)
			second_part = squre_root*difference*(difference - 1) / 2;
		else if (difference == squre_root + 1)
			second_part = squre_root*squre_root*(squre_root - 1) / 2;
		else if (difference > (squre_root + 1))
			second_part = squre_root*squre_root*(squre_root - 1) / 2
			+ (difference - squre_root)*(difference - squre_root - 1) / 2;
	}
	else
	{
		unsigned row_count = K / min_edge;
		first_part = CalculateMartrix(min_edge, row_count);
		difference = K - row_count*min_edge;
		second_part = row_count*difference*(difference - 1) / 2;
	}
	return first_part + second_part;
}
bool CheckTheInput(unsigned N, unsigned M, unsigned K)
{
		return (N > 0 && N <= 30000) && (M > 0 && M <= 30000) 
			    && (K >= 1 && K <= N*M);
}
void PrintTheResult(vector<unsigned>& vec)
{
	unsigned count = 1;
	for (auto element : vec)
	{
		cout << "Case#" << count << ": " << element << endl;
		++count;
	}
}

---------------------------------------------------------------------

四、总结

  在完成一个任务之前,要把任何问题任何场景都想清楚,然后再动手编程,这是最省事答案也是最完美的。只有当这些基本情况你都想清楚了的时候,那么代码在你的控制之下,而不是你在代码的控制之下。
<think> 我们参考了用户提供的三个引用,特别是引用[1]中的高效计算方法。 用户的问题集中在:使用C++算法解决棋盘问题中求长方形个数的方法。 根据引用[1],我们知道: - 棋盘由n行m列的方格组成。 - 正方形的个数a可以通过循环计算:a = sum_{i=0}^{min(n,m)-1} (n-i)*(m-i) - 矩形的总个数(包括正方形和长方形)为:sum = C(n+1,2) * C(m+1,2) = (n*(n+1)/2) * (m*(m+1)/2) = n*(n+1)*m*(m+1)/4 - 长方形个数b = 总矩形个数 - 正方形个数 = sum - a 这种方法比引用[2]和引用[3]中的四重循环方法高效得多,时间复杂度为O(min(n,m)),而四重循环的时间复杂度为O(n^2*m^2),在n和m较大时不可行。 因此,我们推荐使用引用[1]中的方法。 具体步骤: 1. 输入n和m(分别表示棋盘的行数和列数)。 2. 计算正方形的个数a: a = 0 for (long long i = 0; i < n && i < m; i++) { a += (n - i) * (m - i); } 3. 计算矩形的总个数sum:sum = n*(n+1)/2 * m*(m+1)/2 (注意:这里为了防止整数溢出,我们使用long long类型,并且可以先除以2再相乘,但要注意整数除法的取整问题。实际上,由于n(n+1)和m(m+1)中必有一个偶数,所以可以这样计算:sum = (n*(n+1LL) * m*(m+1LL)) / 4; 使用1LL来提升为long long避免溢出) 4. 长方形个数b = sum - a。 5. 输出a和b(正方形个数长方形个数)。 注意:题目中要求的是长方形个数(不包括正方形),所以用总矩形个数减去正方形个数。 我们也可以只计算长方形个数,而不显式计算正方形个数?但上述方法已经足够高效。 代码实现(注意使用long long防止溢出): 例如:n=2, m=3 正方形个数:a = 2*3 + 1*2 = 6+2=8 矩形总个数:sum = (2*3*3*4)/4 = (6*12)/4=72/4=18 长方形个数:b=18-8=10 验证:引用[2]和引用[3]中的方法(四重循环)在n=2,m=3时计算: 正方形个数:8,长方形个数:10,正确。 因此,我们给出如下代码: </think> ### 棋盘问题长方形个数的C++算法解决方法 在方格棋盘问题中,计算长方形个数(不包括正方形)的高效算法基于组合数学原理。以下是详细解决方法: #### 核心公式推导 1. **矩形总数计算** $m \times n$ 网格中所有矩形的数量由行列组合决定: $$ \text{矩形总数} = \binom{n+1}{2} \times \binom{m+1}{2} = \frac{n(n+1)}{2} \times \frac{m(m+1)}{2} $$ 2. **正方形个数计算** 边长$k$的正方形数量为$(n-k+1)(m-k+1)$,总正方形数: $$ \text{正方形总数} = \sum_{k=1}^{\min(n,m)} (n-k+1)(m-k+1) $$ 3. **长方形个数计算** $$ \text{长方形个数} = \text{矩形总数} - \text{正方形个数} $$ #### 高效C++实现 ```cpp #include <iostream> using namespace std; int main() { long long n, m; // 使用long long防止大数溢出 cin >> n >> m; // 1. 计算矩形总数 long long total_rect = (n * (n + 1) / 2) * (m * (m + 1) / 2); // 2. 计算正方形总数 long long squares = 0; for (long long k = 1; k <= min(n, m); k++) { squares += (n - k + 1) * (m - k + 1); } // 3. 计算长方形个数 long long rectangles = total_rect - squares; cout << rectangles; return 0; } ``` #### 算法优势 1. **时间复杂度**:$O(\min(n,m))$,远优于四重循环的$O(n^2m^2)$ 2. **空间复杂度**:$O(1)$,仅用常数变量存储 3. **大数处理**:使用`long long`避免整数溢出 #### 示例验证 对于$2\times3$网格- 矩形总数 $= \binom{3}{2} \times \binom{4}{2} = 3 \times 6 = 18$ - 正方形数 $= (2\times3) + (1\times2) = 8$ - 长方形数 $= 18 - 8 = 10$ (包含1x2矩形7个,1x3矩形2个,2x3矩形1个)[^1] > 此方法通过数学优化显著提升效率,适用于$n,m \leq 10^6$量级的计算场景[^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值