近似乘积

168 篇文章 1 订阅
98 篇文章 1 订阅

近 似 乘 积 近似乘积

题目链接: j z o j   3925 jzoj\ 3925 jzoj 3925

题目

给你一个集合 A A A和一个正整数 n n n。要求找到三个不在集合 A A A中的正整数 x x x , y y y , z z z,使得其乘积尽量接近 n n n,即最小化 ∣ n   –   x ∗ y ∗ z ∣ |n\ –\ x * y * z| n  xyz x x x , y y y , z z z可以相同。
若有多组解,则最小化 x x x;若仍有多组解,则最小化 y y y;若仍有多组解,最小化 z z z

输入

第一行,一个整数 T T T,表示测试数据组数。
每组数据包含两行。第一行开头的整数 m m m,表示集合 A A A有多少个数,随后 m m m个整数,依次表示集合 A A A的每个元素。第二行为一个整数 n n n

输出

输出 T T T行,每行三个整数,依次表示每组数据的答案 x x x , y y y, z z z

样例输入

3
2 2 4
4
1 1
7
2 1 15
90

样例输出

1 1 3
2 2 2
2 5 9

数据范围

40 % 40\% 40% 的数据: 1  ⁣ < =  ⁣ m  ⁣ < =  ⁣ 10 , 1  ⁣ < =  ⁣ n  ⁣ < = 100. 1\! <=\! m\! <=\! 10,1\! <=\! n\! <= 100. 1<=m<=101<=n<=100.
70 % 70\% 70% 的数据: 1  ⁣ < =  ⁣ m , n  ⁣ < =  ⁣ 1000. 1\! <=\! m, n\! <=\! 1000. 1<=m,n<=1000.
100 % 100\% 100% 的数据: 1  ⁣ < =  ⁣ m , n , A i < =  ⁣ 1 0 6 , 1  ⁣ < =  ⁣ T  ⁣ < =  ⁣ 4. 1\! <=\! m, n, A_i <=\! 10^6,1\! <=\! T\! <=\! 4. 1<=m,n,Ai<=1061<=T<=4.

思路

这道题是一个要有优化的模拟。

我们让这三个数 x  ⁣ ⁣    ⁣    ⁣ ⁣ < =    ⁣    ⁣ ⁣ ⁣ ⁣ y  ⁣ ⁣ < =  ⁣ ⁣ z x\!\!\ \!\ \!\!<=\ \!\ \!\!\!\!y\!\!<=\!\!z x  <=  y<=z,那我们就一个一个枚举它们,并且去掉那些不能用的数字。
但是还是会超时,那我们就要继续优化:
我们要让它们的乘积尽可能接近 n n n,那我们设 a n s ans ans为最小的答案(即与 n n n相差的最小值),那我们就可以让它们的乘积小于 n  ⁣ +  ⁣ a n s n\!+\!ans n+ans的时候才看是否要和当前的最优解( a n s ans ans)交换。因为这样,的出来的值要么小于 n n n,要么大于 n n n而且与 n n n的误差小于等于 a n s ans ans,就不会漏掉其它更优的解。

但是还有一个问题,就是当我们只枚举了 x x x或者只枚举了 x x x y y y的时候,又应该怎么判断呢?
很简单,因为我们上面让 x  ⁣ ⁣ < =  ⁣ ⁣ y  ⁣ ⁣ < =  ⁣ ⁣ z x\!\!<=\!\!y\!\!<=\!\!z x<=y<=z,那我们枚举 x x x的时候就让 x ∗ x ∗ x < = n + a n s x*x*x<=n+ans xxx<=n+ans,枚举 y y y的时候就让 x ∗ y ∗ y < = n + a n s x*y*y<=n+ans xyy<=n+ans,就可以了。
(枚举 z z z的时候就让 x ∗ y ∗ z < = n + a n s x*y*z<=n+ans xyz<=n+ans,这个就不用说了吧)

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

int T, m, n, x, ans, ansi, ansj, ansk, a[1000001];
bool in[10000001];

int main() {
	scanf("%d", &T);//读入
	
	for (int i = 1; i <= T; i++) {
		ans = 1000000000;//初始化
		memset(a, 0, sizeof(a));//
		memset(in, 0, sizeof(in));
		
		scanf("%d", &m);//读入
		for (int i = 1; i <= m; i++) {
			scanf("%d", &x);//读入
			in[x] = 1;//记录
		}
		for (int i = 1; i <= 1000001; i++)
			if (!in[i])
				a[++a[0]] = i;//可以用
		scanf("%d", &n);//读入
		
		if (a[1] > n) {//用最小的都还是比要求的值大
			printf("%d %d %d\n", a[1], a[1], a[1]);//只能让三个数都最小
			continue;
		}
		for (int i = 1; a[i] * a[i] * a[i] <= (n + ans) && i <= a[0]; i++)//枚举第一个数
			for (int j = i; a[i] * a[j] * a[j] <= (n + ans) && j <= a[0]; j++)//枚举第二个数
				for (int k = j; a[i] * a[j] * a[k] <= (n + ans) && k <= a[0]; k++)//枚举第三个数
					if (ans > abs(n - a[i] * a[j] * a[k])) {//更小
						ans = abs(n - a[i] * a[j] * a[k]);//更换
						ansi = a[i];
						ansj = a[j];
						ansk = a[k];
					}
		
		printf("%d %d %d\n", ansi, ansj, ansk);//输出
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值