51Nod 2053 被盗的食物 c/c++题解

题目描述

羊村的羊们为了过冬,他们要在夏天的时候存储一些食物。等到冬天时拿出来吃。他们把食物包装成1×1×1的小方块,以便存储和取出来食用。经过了一个夏天后,小羊们存储了A·B·C块食物。他们把食物放到一个长方体的小屋里,A层高,每层有B行,每行有C块食物。
在秋天过后,村长来到小屋,要打开门分发食物了。但是,很不幸,小屋的四周都散落着食物块。经过查证,小偷们从小屋的顶层,前面,后面,和侧面都偷走了一个面的食物,一个面的食物指的是紧贴着某个面食物。所以剩下只有 (A−1)×(B−2)×(C−2) 块食物。为了隐藏罪证,小偷们把剩下的食物块,全部打乱,散落在小屋的四周。所以村长忘记了原来A,B,C到底是多少。
现在给定n,表示剩下的食物数量。计算可能最小和最大被偷走的食物数量。
样例解释:
在样例中,如果原来的数量为32=2×4×4,现在的数量是4=(2-1)×(4-2)×(4-2),则被偷走的是32-4=28块。
如果原来的数量为45=5×3×3,现在的数量是4=(5-1)×(3-2)×(3-2),则被偷走的是45-4=41块。
输入
单组测试数据。
第一行一个整数n(1≤n≤10^9)代表剩下的食物块。
输出
共一行,两个整数用空格隔开,第一个表示最小可能被偷的食物的数量,第二个表示最多可能被偷的食物的数量。
输入样例
4
输出样例
28 41

题解:

前面说起一大堆没用的,题目意思很好懂,就是给定一个n,n = (A-1) * (B-2) * (C-2)
题目要求求出A * B * C的最大值和最小值,注意,最后的答案输出还要减去n
我一开始是想能不能找出什么规律来,但是发现找不出,
那就只能暴力了,但是暴力的话要注意n(n<=109)的范围很大,所以要采用技巧来优化。
参考了别人代码我才搞懂怎么优化的,见下,如果嫌麻烦的话,可以直接看最后的代码。
假设n = i * j * k
方法1: 三重循环法,不做任何优化

for(int i = 1; i <= n; i++)
{
	for(int j = 1; j <= n; j++)
	{
		for(int k = 1; k <= n; k++)
		{
			if(i*j*k == n)
			{
				/**
					分别比较三种情况
					i+1,j+2,k+2
					j+1,i+2,k+2,
					k+1,i+2,j+2
					找出最大最小值
				*/
			}
		}
	}
}

方法2: 还是三重循环,但是i <= j <= k

for(int i = 1; i <= n; i++)
{
	for(int j = i; j <= n; j++)
	{
		for(int k = j; k <= n; k++)
		{
			if(i*j*k == n)
			{
				/**
					因为i <= j <= k,只有一种情况
					比如 i = 1,j = 2, k =3
					要得到最大值,就需要最小的+2 => i+2,j+2,k+1 => 3,4,4,3*4*4 = 12
					要得到最小值,就需要最小的+1 => i+1,j+2,k+2 => 2,2,5,2*2*5 = 10
					找出最大最小值即可
				*/
			}
		}
	}
}

方法3:两层循环

for(int i = 1; i <= n; i++)
{
	for(int j = 1; j <= n; j++)
	{
				int k = n / (i*j);
				/**
					分别比较三种情况(因为没有k不一定比j大)
					i+1,j+2,k+2
					j+1,i+2,k+2,
					k+1,i+2,j+2
					找出最大最小值
				*/
		}
	}
}

以上的三种方法,发现都过不了,也确实不可能过的了,因为n的范围是1e9,时间上限才1s,这么开循环最多开一层。
这里就需要用到一点点数学的思维方法了:
i,j,k三个数相乘等于n,那么最小的值i(假设是i),i一定是 <= n的立方根 的,所以我们第一层循环只需要开到[1,n的立方根],
得到一个使得 n % i = 0的i,然后n / i = t_n就相当于是j*k的结果了,同样,假设j是较小的数,那么j一定是 <= t_n的平方根 的,所以我们的第二层循环只需要开到[i,t_n的平方根],最后得到t_n % j = 0,那么t_n / j = tt_n,这个tt_n实际上就是k了,而且一定满足 i <= j <= k,所以就以及出来了,稍微分析一下时间复杂度:
第一层for循环,1e9的立方根 = 1e3,
第二层for循环,1e6的平凡根 = 1e3,
总共也才1e6,1s的时间肯定可以过的。最后注意一下:整数都要开到long long才能过!

AC代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <climits>
#include <cstring>
#include <string>
#include <algorithm>
#include <vector>
#include <deque>
#include <list>
#include <utility>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <bitset>
#include <iterator>
using namespace std;

typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll  INF = 0x3f3f3f3f3f3f3f3f;
const double PI = acos(-1.0);
const double E = exp(1.0);
const int MOD = 1e9+7;
const int MAX = 1e5+5;
int n;
vector <int> vec;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    while(cin >> n)
    {
        ll min_num,max_num;
        min_num = INF;
        max_num = -INF;
        for(int i = 1; i <= pow(n,1.0/3); i++)
        {
            if(n % i == 0)
            {
                ll t_n = n / i;
                for(int j = i; j <= sqrt(t_n); j++)
                {
                    if(t_n % j == 0)
                    {
                        ll tt_n = t_n / j;
                        ll k = tt_n;
                        ll tmp_min = (i+1)*(j+2)*(k+2);
                        ll tmp_max = (i+2)*(j+2)*(k+1);
                        if(tmp_max > max_num) max_num = tmp_max;
                        if(tmp_min < min_num) min_num = tmp_min;
                    }
                }
            }
        }
        cout << (min_num-n) << " " << (max_num-n) << endl;
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值