LiberOJ - 2603. 「NOIP2012」国王游戏
算法
(贪心) O ( n 2 ) O(n^2) O(n2)
我们先给出做法,再证明其正确性。
做法:直接将所有大臣按左右手上的数的乘积从小到大排序,得到的序列就是最优排队方案。
证明:
我们记第
i
i
i 个大臣左手上的数是
A
i
A_i
Ai,右手上的数是
B
i
B_i
Bi。
假设当前的排队方案不是按
A
i
∗
B
i
A_i * B_i
Ai∗Bi 从小到大排序的,则一定存在某两个相邻的人,满足
A
i
∗
B
i
>
A
i
+
1
∗
B
i
+
1
A_i * B_i > A_{i+1} * B_{i+1}
Ai∗Bi>Ai+1∗Bi+1。
我们现在将这两个人的位置互换,然后考虑他们在交换前和交换后所获得的奖励是多少:
- 交换前:第 i i i 个人是 ∏ j = 0 i − 1 A j B i \frac{\prod_{j=0}^{i-1}A_j}{B_i} Bi∏j=0i−1Aj,第 i + 1 i + 1 i+1 个人是 ∏ j = 0 i A j B i + 1 \frac{\prod_{j=0}^{i}A_j}{B_{i + 1}} Bi+1∏j=0iAj;
- 交换后:第 i i i 个人是 ∏ j = 0 i − 1 A j B i + 1 \frac{\prod_{j=0}^{i-1}A_j}{B_{i + 1}} Bi+1∏j=0i−1Aj,第 i + 1 i + 1 i+1 个人是 A i + 1 ∗ ∏ j = 0 i − 1 A j B i \frac{A_{i + 1} * \prod_{j=0}^{i - 1}A_j}{B_i} BiAi+1∗∏j=0i−1Aj;
由于我们接下来只比较这四个数的大小关系,而且所有 A i , B i A_i, B_i Ai,Bi 均大于0,所以可以将每个数除以 ∏ j = 0 i − 1 A j \prod_{j=0}^{i-1}A_j ∏j=0i−1Aj,然后乘 B i ∗ B i + 1 B_i * B_{i + 1} Bi∗Bi+1,
由于
A
i
>
0
A_i > 0
Ai>0,所以
B
i
≤
A
i
∗
B
i
B_i \le A_i * B_i
Bi≤Ai∗Bi,并且
A
i
∗
B
i
>
A
i
+
1
∗
B
i
+
1
A_i * B_i > A_{i+1} * B_{i+1}
Ai∗Bi>Ai+1∗Bi+1,所以
m
a
x
(
B
i
,
A
i
+
1
∗
B
i
+
1
)
≤
A
i
∗
B
i
≤
m
a
x
(
B
i
+
1
,
A
i
∗
B
i
)
max(B_i, A_{i + 1} * B_{i + 1}) \le A_i * B_i \leq max(B_{i + 1}, A_i * B_i)
max(Bi,Ai+1∗Bi+1)≤Ai∗Bi≤max(Bi+1,Ai∗Bi), 所以交换后两个数的最大值不大于交换前两个数的最大值。
而且交换相邻两个数不会对其他人的奖金产生影响,所以如果存在逆序,则将其交换,得到的结果一定不会比原来更差。
所以从小到大排好序的序列就是最优解,证毕。
时间复杂度
排序的时间复杂度是
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。
这道题目的时间复杂度瓶颈在高精度计算上,最坏情况下所有
A
i
=
9999
A_i = 9999
Ai=9999,则前
i
i
i 个数的乘积大约是
4
i
4i
4i 位,每次乘一个新的数就需要
4
i
4i
4i 的计算量,所以总共的计算量是
O
(
4
∗
∑
i
=
1
n
i
)
=
O
(
n
2
)
O(4 * \sum_{i = 1}^ni) = O(n^2)
O(4∗∑i=1ni)=O(n2)。
这里我们用 C++ 代码和 Java 代码同时实现本道题
C++ 代码
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<int, int> PII;
const int N = 1010;
int n;
PII p[N];
vector<int> mul(vector<int>a, int b)
{
vector<int> c;
int t = 0;
for (int i = 0; i < a.size(); i ++ )
{
t += a[i] * b;
c.push_back(t % 10);
t /= 10;
}
while (t)
{
c.push_back(t % 10);
t /= 10;
}
return c;
}
vector<int> div(vector<int>a, int b)
{
vector<int> c;
bool is_first = true;
for (int i = a.size() - 1, t = 0; i >= 0; i -- )
{
t = t * 10 + a[i];
int x = t / b;
if (!is_first || x)
{
is_first = false;
c.push_back(x);
}
t %= b;
}
reverse(c.begin(), c.end());
return c;
}
vector<int> max_vec(vector<int> a, vector<int> b)
{
if (a.size() > b.size()) return a;
if (a.size() < b.size()) return b;
if (vector<int>(a.rbegin(), a.rend()) > vector<int>(b.rbegin(), b.rend())) return a;
return b;
}
int main()
{
cin >> n;
for (int i = 0; i <= n; i ++ )
{
int a, b;
cin >> a >> b;
p[i] = {a * b, a};
}
sort(p + 1, p + n + 1);
vector<int> product(1, 1);
vector<int> res(1, 0);
for (int i = 0; i <= n; i ++ )
{
if (i) res = max_vec(res, div(product, p[i].first / p[i].second));
product = mul(product, p[i].second);
}
for (int i = res.size() - 1; i >= 0; i -- ) cout << res[i];
cout << endl;
return 0;
}
Java代码
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Scanner;
class Node implements Comparable<Node> {
int l, r;
@Override
public int compareTo(Node o) {
return l * r - o.l * o.r;
}
}
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
Node[] q = new Node[n+1];
int a = sc.nextInt();
int b = sc.nextInt();
for (int i = 1; i <= n; i++) {
q[i] = new Node();
q[i].l = sc.nextInt();
q[i].r = sc.nextInt();
}
Arrays.sort(q, 1, n + 1);
BigInteger res = BigInteger.ZERO;
BigInteger L = BigInteger.valueOf(a);
for (int i = 1; i <= n; i++) {
res = res.max(L.divide(BigInteger.valueOf(q[i].r)));
L = L.multiply(BigInteger.valueOf(q[i].l));
}
System.out.println(res);
}
}