AcWing 114. 【0x07】国王游戏

题目描述

恰逢 \(H\) 国国庆,国王邀请 \(n\) 位大臣来玩一个有奖游戏。

首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、右手上各写一个整数。

然后,让这 \(n\) 位大臣排成一排,国王站在队伍的最前面。

排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:

排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。

国王不希望某一个大臣获得特别多的奖赏,所以他想请你帮他重新安排一下队伍的顺序,使得获得奖赏最多的大臣,所获奖赏尽可能的少。

注意,国王的位置始终在队伍的最前面。

输入格式

第一行包含一个整数 \(n\),表示大臣的人数。

第二行包含两个整数 \(a\)\(b\),之间用一个空格隔开,分别表示国王左手和右手上的整数。

接下来 \(n\) 行,每行包含两个整数 \(a\)\(b\),之间用一个空格隔开,分别表示每个大臣左手和右手上的整数。

输出格式

输出只有一行,包含一个整数,表示重新排列后的队伍中获奖赏最多的大臣所获得的金币数。

数据范围

\(1 \le n \le 1000\)
\(0 < a,b <10000\)

输入样例:

3
1 1
2 3
7 4
4 6

输出样例:

2

算法一.贪心 \(O(n^2)\)

这是一个非常典型的贪心问题。

我们记第 \(i\) 个大臣左手上的数是 \(q[i].left\),右手上的数是 \(q[i].right\)。针对于第 \(x\) 个大臣和第 \(x+1\) 个大臣,二者获得的奖赏分别为:

\(x,x+1:\)

  • \(x\) 个大臣:\(\frac{\prod_{i=0}^{x-1}q[i].left}{q[x].right}=\frac{1}{q[x].right}\prod_{i=0}^{x-1}q[i].left\)
  • \(x+1\) 个大臣:\(\frac{\prod_{i=0}^{x}q[i].left}{q[x+1].right}=\frac{q[x].left}{q[x+1].right}\prod_{i=0}^{x-1}q[i].left\)

如果第 \(x\) 个大臣和第 \(x+1\) 个大臣的位置发生了交换,二者获得的奖赏分别为:

\(x+1,x:\)

  • \(x+1\) 个大臣:\(\frac{\prod_{i=0}^{x-1}q[i].left}{q[x+1].right}=\frac{1}{q[x+1].right}\prod_{i=0}^{x-1}q[i].left\)
  • \(x\) 个大臣:\(\frac{\prod_{i=0}^{x-1}q[i].left×q[x+1].left}{q[x].right}=\frac{q[x+1].left}{q[x].right}\prod_{i=0}^{x-1}q[i].left\)

因为四个数均 \(>0\),那么去除 \(\prod_{i=0}^{x-1}q[i].left\) 这个公因子不影响大小的比较;

据题意,我们要让获得最多奖赏的人获得的奖赏最少:

\(max(\frac{1}{q[x].right},\frac{q[x].left}{q[x+1].right})\)\(max(\frac{1}{q[x+1].right},\frac{q[x+1].left}{q[x].right})\) 进行比较,哪一个方案的值更小选哪个。

同时乘以 \(q[x].right×q[x+ 1].right\) 不影响大小比较结果,得:

\(max(q[x+ 1].right,q[x].right×q[x].left)\)\(max(q[x].right,q[x+1].left×q[x+1].right)\)

因为任意 \(q[i].right\)\(q[i].left\) 均是正整数大于 \(1\)

\(q[x].right×q[x].left>q[x].right\)\(q[x+1].left×q[x+1].right>q[x+ 1].right\)

实际上我们比较 \(q[x].right×q[x].left\)\(q[x+1].left×q[x+1].right\) 的大小即可。

因此,当

  • \(q[x+1].left×q[x+1].right\) > \(q[x].right×q[x].left\) 时,\(x,x+1\) 方案好;

  • \(q[x].right×q[x].left\) > \(q[x+1].left×q[x+1].right\) 时,\(x+1,x\) 方案好;

综上所述,大臣的排序可以按照 \(q[i].right×q[i].left\) 的值从小到大升序排序。

依次顺序计算最大的奖赏即可,但是只能过 \(7/11\) 个测试点。

C++ 代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1050;
int n;
struct node {
    int left, right;
} q[N];

bool cmp(node x, node y) { return x.left * x.right < y.left * y.right; }

int main() {
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    cin >> q[0].left >> q[0].right;
    for (int i = 1; i <= n; i++) cin >> q[i].left >> q[i].right;
    sort(q + 1, q + n + 1, cmp);
    ll ans = -1;
    for (int i = 1; i <= n; i++) {
        ll num = 1;
        for (int j = 0; j < i; j++) {
            num *= q[j].left;
        }
        num /= q[i].right;
        ans = max(ans, num);
    }
    cout << ans;
    return 0;
}

算法二.贪心+高精度 $

针对于第 \(x\) 个大臣的奖赏,\(\prod_{i=0}^{x-1}q[i].left\) 需要高精度,\(\frac{\prod_{i=0}^{x-1}q[i].left}{q[x].right}\) 需要高精度;

因此开两个数组 \(num[N]\)\(ans[N]\) 高精度数组;

  • \(num[N]\) 存每一个大臣的奖赏,即 \(\frac{\prod_{i=0}^{x-1}q[i].left}{q[x].right}\)
  • \(ans[N]\)\(n\) 个大臣的奖赏的最大值。

C++ 代码(已将低精度代码更换为了高精度,高精度算法细节不再解释)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1050;
const int M = 40000;
int n, ans[M], len_a, num[M], len_n;
struct node {
    int left, right;
} q[N];

bool cmp(node x, node y) { return x.left * x.right < y.left * y.right; }

void cul_1(int x) {	//num *= x
    for (int i = 0; i < len_n; i++) {
        num[i] *= x;
    }
    for (int i = 0; i < len_n - 1; i++) {
        num[i + 1] += num[i] / 10;
        num[i] %= 10;
    }
    while (num[len_n - 1] >= 10) {
        num[len_n] = num[len_n - 1] / 10;
        num[len_n - 1] %= 10;
        len_n++;
    }
}

void cul_2(int x) {//num /= x
    int temp[M], len_t = 0, t = 0;
    for (int i = len_n - 1; i >= 0; i--) {
        t = 10 * t + num[i];
        temp[len_t++] = t / x;
        t %= x;
    }
    len_n = len_t;
    memset(num, 0, sizeof(num));
    for (int i = 0; i < len_t; i++) {
        num[len_t - 1 - i] = temp[i];
    }
}

bool com() {	//if(num > ans)
    if (len_a != len_n)
        return len_n > len_a;
    for (int i = len_a - 1; i >= 0; i--) {
        if (num[i] < ans[i]) {
            return false;
        }
        if (num[i] > ans[i]) {
            return true;
        }
    }
    return false;
}

void cul_3() { //ans = num
    memset(ans, 0, sizeof(ans));
    len_a = len_n;
    for (int i = 0; i < len_n; i++) {
        ans[i] = num[i];
    }
}

void getout_a() {	//cout << ans;
    while (ans[len_a - 1] == 0 && len_a > 1) len_a--;
    for (int i = len_a - 1; i >= 0; i--) cout << ans[i];
}

int main() {
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    cin >> q[0].left >> q[0].right;
    for (int i = 1; i <= n; i++) cin >> q[i].left >> q[i].right;
    sort(q + 1, q + n + 1, cmp);
    for (int i = 1; i <= n; i++) {
        memset(num, 0, sizeof(num));
        num[0] = 1, len_n = 1;	//num = 1;
        for (int j = 0; j < i; j++)
            cul_1(q[j].left);	//num *= q[j].left;
        cul_2(q[i].right);	//num /= q[i].right;
        if (com())	cul_3();	//if(num > ans)	ans = num;
    }
    getout_a();	//cout << ans;
    return 0;
}

但是只能过 \(8/11\) 个测试点。

算法三.贪心+高精度+优化

我们发现,针对于每一个大臣,都会计算前面所有人的左手成绩,即:

当我们计算第 \(x\) 个大臣前面的 \(\prod_{i=0}^{x-1}q[i].left\) 时,其实在 计算第 \(x-1\) 个大臣时,就完成了 \(\prod_{i=0}^{x-2}q[i].left\)

即左手的累乘结果完全可以避免大量的重复计算,当第 \(x\) 个大臣要使用时,只需要对于已有的 \(\prod_{i=0}^{x-2}q[i].left\) 乘上一个 \(q[x-1].left\) 即可。

C++ 代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1050;
const int M = 40000;
int n, ans[M], len_a, num[M], sum[M], len_s, len_n;
struct node {
    int left, right;
} q[N];

bool cmp(node x, node y) { return x.left * x.right < y.left * y.right; }

void cul_1(int x) {
    for (int i = 0; i < len_n; i++) num[i] *= x;
    for (int i = 0; i < len_n - 1; i++) {
        num[i + 1] += num[i] / 10;
        num[i] %= 10;
    }
    while (num[len_n - 1] >= 10) {
        num[len_n] = num[len_n - 1] / 10;
        num[len_n - 1] %= 10;
        len_n++;
    }
}

void cul_2(int x) {
    int temp[M], len_t = 0, t = 0;
    for (int i = len_n - 1; i >= 0; i--) {
        t = 10 * t + num[i];
        temp[len_t++] = t / x;
        t %= x;
    }
    len_s = len_t;
    memset(sum, 0, sizeof(sum));
    for (int i = 0; i < len_t; i++) 
        sum[len_t - 1 - i] = temp[i];
    while (sum[len_s - 1] == 0 && len_s > 1) len_s--;
}

bool com() {
    if (len_a != len_s)
        return len_s > len_a;
    for (int i = len_a - 1; i >= 0; i--) {
        if (sum[i] < ans[i])	return false;
        if (sum[i] > ans[i])	return true;
    }
    return false;
}

void cul_3() {
    memset(ans, 0, sizeof(ans));
    len_a = len_s;
    for (int i = 0; i < len_s; i++)
        ans[i] = sum[i];
}

void getout_a() {
    while (ans[len_a - 1] == 0 && len_a > 1) len_a--;
    for (int i = len_a - 1; i >= 0; i--) cout << ans[i];
}

int main() {
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    cin >> q[0].left >> q[0].right;
    for (int i = 1; i <= n; i++) cin >> q[i].left >> q[i].right;
    sort(q + 1, q + n + 1, cmp);
    num[0] = 1, len_n = 1;
    for (int i = 1; i <= n; i++) {
        cul_1(q[i - 1].left);
        cul_2(q[i].right);
        if (com())	cul_3();
    }
    getout_a();
    return 0;
}

可以过 \(11/11\) 个数据点,得到 \(Accepted\)

posted @ 2025-08-23 09:58  步履不停的赤子  阅读(8)  评论(0)    收藏  举报