[转]编程之美 2013 全国挑战赛 资格赛 题目二 长方形

题目二 长方形

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

描述

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

输入

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

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

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

输出

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

数据范围

1 ≤ T ≤ 100

0 ≤ K ≤ N * M

小数据:0 < N, M ≤ 30

大数据:0 < N, M ≤ 30000

样例输入

3
3 3 8
4 5 13
7 14 86

样例输出

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

解题思路

这道题算是一道数学题,将所有石子摆放成下面的形状能组成最多的长方形应该是显然的,其中有 2aN,2bM,r0,a×b+r=K。现在就是要求出最优的 a,b,r 使得组成的长方形个数最多。

图 1 石子摆放形式

一个最先需要解决的最基本的问题就是如何计算长方形的个数。对于 a×b 的石子组成的长方形,包含的所有长方形的个数为 ab(a1)(b1)4,推导过程如下所示:

任何一个长方形都是由四个顶点组成的,但只要知道了左上角顶点和右下角顶点就可以唯一确定一个长方形,因此长方形的个数就等于唯一的左上角和右下角顶点对的个数。为了方便计算,设石子的最左上角位于坐标 (1,1),右下方为正方向,如图所示。

图 2 坐标轴

对于 a×b 的石子组成的长方形,可以作为左上角的顶点有 (a1)(b1) 个(最底下的一行和最后边的一列不能作为左上角顶点),设现在的左上角坐标为 (x,y)。对于每个左上角 (x,y),相应的可作为右下角的顶点有 (ax)(by) 个(在上图中,若红色顶点为左上角,那么可能的右下角顶点一定是绿色的顶点之一),那么只要将所有左上角对应的右下角顶点的个数累加起来就是最后的结果,即为:

 

i=1aj=1b(ai)(bj)

 

这个公式求和很容易:

 

i=1aj=1b(ai)(bj) =i=1a(ai)j=1b(bj) =a(a1)2b(b1)2=ab(a1)(b1)4

 

有了公式,那么就可以枚举得到长方形个数的最大值了,为了简单起见,可以令 NM。设长方形有 a 行石子,那么枚举的范围是 2aK−−√r=K,长方形石子的列数 b=(Kr)/a。如果有行数或列数超出超出范围的,要记得忽略掉。

余数 r 的处理则比较复杂。这 r 个点一般情况下应当像图 1 一样添加到最后一列的后面,因为上面 a 的范围保证了一般情况下 ab,多余的点作为新一列添加显然能组成更多的长方形。多出来的长方形数量应该是以 r 中的石子作为右下角的长方形数量,或者看成 (b+1)×r 的石子包含的长方形数量减去 b×r 的石子包含的长方形数量(要除去重复的长方形数量)。结果为 br(r1)2,r2

然后就要考虑不一般的情况了,一种是 r<2,这时无论如何都不能组成长方形,无视它就好;还有一种是 b+1 可能会大于 M(此时 b=M),就必须将 r 个石子作为新一行添加到最后一行的后面,这时是可以保证 a <N 的,因为 K<N×M;最后一种情况是 a>b,这种情况只会出现在 a=K−−√ 时,这时也应当将 r 个石子添加为新一行,但要保证 i+1 n

对了,还有最后一点:如果要过大数据的话,里面的 a,b 都要用 long long 的,数据的最大值大概是 300004,还在 long long 的范围内。

代码

复制代码
 1 #include <stdio.h>
 2 #include <math.h>
 3 // 计算 a*b 的石子中包含的长方形数
 4 long long calRects(long long a, long long  b) {
 5     return a*b*(a-1)*(b-1)/4;
 6 }
 7 // 计算剩余的 r 个石子可以包含的长方形个数
 8 long long calExtRects(long long t, long long r) {
 9     return t*r*(r-1)/2;
10 }
11 
12 int main() {
13     int T, n, m, k;
14     scanf("%d ", &T);
15     for (int caseIdx = 1;caseIdx <= T;caseIdx++) {
16         scanf("%d%d%d", &n, &m, &k);
17         // 保证 n <= m
18         if (n > m) {
19             int t = n;
20             n = m;
21             m = t;
22         }
23         long long max = 0;
24         int maxa = (int)ceil(sqrt((double)k));
25         if (maxa > n) maxa = n;
26         for (int a = 2;a <= maxa;a++){
27             int r = k % a;
28             int b = (k - r) / a;
29             if (b > m) continue;
30             long long cnt = calRects(a, b);
31             if (r >= 2) {
32                 // 添加剩余的 r 个石子为新一列
33                 int t = b;
34                 if (b == m || (a < n && a > b && r < b)) {
35                     // 需要将石子添加为新一行的情况
36                     t = a;
37                 }
38                 cnt += calExtRects(t, r);
39             }
40             if (cnt > max) max = cnt;
41         }
42         printf("Case #%d: %lld\n", caseIdx, max);
43     }
44     return 0;
45 }
复制代码

 

作者:CYJB 
出处:http://www.cnblogs.com/cyjb/ 
GitHub:https://github.com/CYJB/ 
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

转载于:https://www.cnblogs.com/warner/archive/2013/04/14/3021285.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值