算法笔记:国王游戏

题目来源:thttps://oj.haizeix.com/problem/256
题目描述

​ 恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏。首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、右手上各写一个整数。然后,让这 n 位大臣排成一排,国王站在队伍的最前面。排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。

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


输入

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

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

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

输出

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


样例输入
3
1 1
2 3
7 4
4 6
样例输出
2

数据规模与约定

​ 时间限制:1 s

​ 内存限制:256 M

​ 100% 的数据保证 1≤n≤1000

解决这道题所需的一个算法思想叫“微扰”。所谓微扰,就是微微地干扰一下。我们先来对题目进行分析:

1.首先我们有一个初始的大臣(可简化为一个二元组)序列,每一个二元组都有一个与其相对应的值(也就是每一个大臣可获得的金币数量)。我们分别用L和R来代表左手上的数字和右手上的数字,i来代表第几个大臣。例如Li就是第i个大臣的左手数字。设大臣获得金币数量为C,那么我们可以得出每一个大臣可以获得的金币数量公式为:                           

                                                       Ci=(\prod_{j=0}^{i-1}L_{j})/Ri

2.接下来我们开始微扰:假设我们已经知道获得金币最多的大臣时第i+1个,获得的金币数为C(i+1),我们可以调换一下他与他前面的那个大臣,也就是第i位大臣的位置。之后我们可以对比调换前后的两个序列的金币最大值,而我们所希望的是:调换之后的序列的金币最大值要小于调换前的,这样就代表着我们至少走对了一步。

上面是调换前,下面是调换后

值得说明的是,我们不难(?)发现,我们调换位置后,只有被调换的两个C值会发生变化,其他的C值都照常不变。

3.那么要想走对这一步,代价是什么呢?我的意思是,我们需要Ci和C(i+1)满足什么样的条件呢?我们无需知道具体是哪一个C成为了调换后序列的最大值,我们只需要知道,调换后的最大值确实变小了。不难看出, 除了被调换的这两项,其余的C都是要小于我们的C(i+1)的,那么我们只需要证明被调换后,值被改变的Ci和C(i+1)的值都小于调换前的C(i+1)即可。

我们代入公式:

                                                      Ci=(\prod_{j=0}^{i-1}L_{j})/Ri

                                              C_{i+1}=((\prod_{j=0}^{i-1}L_{j})*L_{i})/R_{i+1}

                                               Ci{}'=((\prod_{j=0}^{i-1}L_{j})*L{_{j+1}}^{})/Ri

                                                  C_{i+1}{}'=(\prod_{j=0}^{i-1}L_{j})/R_{i+1}

我们不难发现,调换后的C(i+1)肯定小于原本的C(i+1),那么我们只需比较C(i+1)与调换后的Ci即可,将公式化简得:

                                                    L_{i}*R_{i}\geq L_{i+1}*R_{i+1}

达成这个条件,我们就可以知道,调换后的最大值小于调换前的最大值。我们可以把这个公式推广一下:对于任意两个相邻的大臣,只要他们满足以上公式,那么他们在调换位置之后,至少在他们二人中,最大值会变得更小。继续把这个公式推广至所有大臣:如果对于每一对相邻的大臣而言都不在满足这个条件,那么是不是就可以说明,不会再出现更小的最大值了呢?这其实就相当于以这个公式为我们的排序条件,对所有大臣进行排序,就可以解决这道题。

代码如下:

//256. 国王游戏
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

int main()
{
	int n;
	cin >> n;
	vector<pair<long long, long long>> v;
	for (int i = 0; i <= n; i++)
	{
		pair<long long, long long> p;
		cin >> p.first;//first和second分别代表左手和右手
		cin >> p.second;
		v.push_back(p);
	}
	sort(v.begin() + 1, v.end(), [&](pair<long long, long long> a, pair<long long, long long> b) ->bool
		{
			return a.first * a.second < b.first * b.second;
		});
	long long total = v[0].first;
	long long Max = 0;
	for (int i = 1; i < v.size(); i++)
	{
		Max = max(Max, total / v[i].second);
		total *= v[i].first;
	}
	cout << Max;
}

但我们把这份代码提交上去后,却只有60分,这么绘事呢?观察未通过的四个检查点我们可以发现,其要求的答案都是非常大的数,超过了2的32次方,甚至超过了long long的表示范围,这说明,虽然咱们的思路是对的,算法也没有问题,但普通的代码实现并不能完全解决这道题。我们还需要实现一个大整数类。这可能需要重构之前的部分代码。

//256. 国王游戏
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

#define MAX_N 1000
int a[MAX_N + 5], b[MAX_N + 5], ind[MAX_N + 5];

class BigInt : public vector<int> { // 1234 -> [4, 3, 2, 1]
public :
    BigInt(int x) {
        this->push_back(x);
        proccess_digit();
        return ;
    }
    BigInt operator/(int x) {
        BigInt ret(*this);
        int y = 0;
        for (int i = size() - 1; i >= 0; i--) {
            y = y * 10 + at(i);
            ret[i] = y / x;
            y %= x;
        }
        ret.proccess_digit();
        return ret;
    }
    void operator*=(int x) {
        for (int i = 0; i < size(); i++) at(i) *= x;
        proccess_digit();
        return ;
    }
    bool operator>(const BigInt &a) const {
        if (size() != a.size()) return size() > a.size();
        for (int i = size() - 1; i >= 0; i--) {
            if (at(i) != a[i]) return at(i) > a[i];
        }
        return false;
    }
    void proccess_digit() {
        for (int i = 0; i < size(); i++) {
            if (at(i) < 10) continue;
            if (i + 1 == size()) this->push_back(0);
            at(i + 1) += at(i) / 10;
            at(i) %= 10;
        }
        while (size() > 1 && at(size() - 1) == 0) this->pop_back();
        return ;
    }
};

ostream &operator<<(ostream &out, const BigInt &a) {
    for (int i = a.size() - 1; i >= 0; i--) {
        out << a[i];
    }
    return out;
}

int main() {
    int n;
    cin >> n;
    for (int i = 0; i <= n; i++) {
        cin >> a[i] >> b[i];
        ind[i] = i;
    }
    sort(ind + 1, ind + n + 1, [&](int i, int j) -> bool {
        return a[i] * b[i] < a[j] * b[j];
    });
    BigInt p = a[0], ans = 0, temp = 0;
    for (int i = 1; i <= n; i++) {
        temp = p / b[ind[i]];
        if (temp > ans) ans = temp;
        p *= a[ind[i]];
    }
    cout << ans << endl;
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值