近 似 乘 积 近似乘积 近似乘积
题目链接: 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 – x∗y∗z∣ 。
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<=10,1<=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<=106,1<=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
x∗x∗x<=n+ans,枚举
y
y
y的时候就让
x
∗
y
∗
y
<
=
n
+
a
n
s
x*y*y<=n+ans
x∗y∗y<=n+ans,就可以了。
(枚举
z
z
z的时候就让
x
∗
y
∗
z
<
=
n
+
a
n
s
x*y*z<=n+ans
x∗y∗z<=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;
}