[NOIP2012 提高组] 国王游戏(C++,贪心,高精度)

本文介绍了一个游戏奖励分配问题,通过合理调整队伍顺序,使获得最高奖励的玩家所得奖励最小化。利用高精度算法处理大数运算,确保计算准确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

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

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

输入格式

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

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

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

输出格式

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

样例 #1

样例输入 #1

3 
1 1 
2 3 
7 4 
4 6

样例输出 #1

2

提示

【输入输出样例说明】

111222333 这样排列队伍,获得奖赏最多的大臣所获得金币数为 222

111333222 这样排列队伍,获得奖赏最多的大臣所获得金币数为 222

222111333 这样排列队伍,获得奖赏最多的大臣所获得金币数为 222

按$ 2$、333、$1 $这样排列队伍,获得奖赏最多的大臣所获得金币数为 999

333111、$2 $这样排列队伍,获得奖赏最多的大臣所获得金币数为 222

按$ 3$、222111 这样排列队伍,获得奖赏最多的大臣所获得金币数为 999

因此,奖赏最多的大臣最少获得 222 个金币,答案输出 222

【数据范围】

对于 20%20\%20% 的数据,有 1≤n≤10,0<a,b<81≤ n≤ 10,0 < a,b < 81n10,0<a,b<8

对于 40%40\%40% 的数据,有$ 1≤ n≤20,0 < a,b < 8$;

对于 60%60\%60% 的数据,有 1≤n≤1001≤ n≤1001n100

对于 60%60\%60% 的数据,保证答案不超过 10910^9109

对于 100%100\%100% 的数据,有 1≤n≤1,000,0<a,b<100001 ≤ n ≤1,000,0 < a,b < 100001n1,000,0<a,b<10000

解题思路:

先不考虑如何使最大值尽可能的小,只针对某一位固定的大臣

为了使这位大臣所获得的金币数最少,我们希望他前面所有人左手上的数乘积尽可能的小,同时这位大臣右手上的数尽可能的大

要使乘积尽可能的小,需要使乘数尽可能的少、使排队的人左手上的数尽可能的小

所以现在有三个目的:该大臣前面的人数尽可能的少,前面的人左手上的数尽可能的小,他右手上的数尽可能的大

以上三条就是减少一位大臣获得奖赏的所有方法

然后我们来考虑如何使最大值尽可能的小

对于第一条,只需要让最大值站在国王后面就可以了,但这显然不合理

对于第二条,前面的人左手上的数尽可能的小,可以说就是后面的人左手上的数尽可能的大

也就是说左手数越大的大臣,我们偏向于让他站在后面

对于第三条,我们偏向于让右手数越大的大臣站在越后面

那么是不是左右手乘积越大站在越后面就行了呢?接下来进行证明

现假设有n位大臣,编号分别为p1,p2,…,pnp_1,p_2,\dots,p_np1,p2,,pn,其中获得奖赏最多的为pk(1≤k≤n)p_k(1 \leq k \leq n)pk(1kn)

要使max减少,需要进行位置交换,假设将pi,pj(1≤i≤k≤j≤n,i≠j)p_i,p_j(1 \leq i \leq k \leq j \leq n,i \ne j)pi,pj(1ikjn,i=j)进行交换可以使max减少

可以看到,对于所有pip_ipi之前和pjp_jpj之后的大臣,其获得的金币数不受影响

设左手数数组、右手数数组分别为left[n + 1],right[n + 1]

接下来分类讨论三种情况

(1)i != k && j != k

由于pkp_kpk的右手数不会改变,前方人数不变,max减少一定是因为第二条

可以看到,pi+1p_{i+1}pi+1~pj−1p_{j-1}pj1所获得的奖赏均减少

同时由于pjp_jpj的右手数不变,且因为第一条,所以pjp_jpj获得金币数减少

要使max减少,只需要使pi≤maxp_i \leq maxpimaxpkp_kpk即可

可以推导出newpi=left1∗⋯∗leftj−1∗leftjleftirightj∗rightirightj=oldpj∗leftj∗rightjlefti∗rightinewp_i = \frac{left_1* \dots *left_{j-1}* \frac{left_j}{left_i}}{right_j* \frac{right_i}{right_j}} = oldp_j * \frac{left_j * right_j}{left_i * right_i}newpi=rightjrightjrightileft1leftj1leftileftj=oldpjleftirightileftjrightj(注:这个式子除了i<ji < ji<j之外没有任何限制)

若有pip_ipi左右手乘积更大,则有newpi<oldpj≤maxnewp_i < oldp_j \leq maxnewpi<oldpjmax

(2)i == k && j != k

由于pkp_kpk的右手数不会改变,前方大臣左手数乘积增大,显然不可能使max减小

(3)i != k && j == k

显然max减少是因为第一条,有rightk>rightiright_k > right_irightk>righti

由(1)式子可得,如果pip_ipi的左右手乘积更大,newpi<oldpk≤maxnewp_i < oldp_k \leq maxnewpi<oldpkmax

leftk≤leftileft_k \leq left_ileftklefti,则pi+1p_{i+1}pi+1~pk−1p_{k-1}pk1所获得的的奖赏均减少,故max减少了

leftk>leftileft_k > left_ileftk>lefti,显然pkp_kpk的左右手乘积更大,不能和pip_ipi交换

至此,证明完毕

接下来是代码的实现,注意可能会出现100099991000^{9999}10009999故需要使用高精度

#include <iostream>
#include <algorithm>
#include <iomanip>
//#include <fstream>//Debug
using namespace std;

const size_t bits = 10;//1e10进制
const int64_t mod_num = 10000000000;//1e10

struct person {
	int left;
	int right;
};

person queue[1001];
int64_t num[2][10000] = { {1},{1} };//10000 * bits位
int64_t max_num[10000] = { 0 };//2^63 - 1 = 9 223 372 036 854 775 807
int max_len = 0;
//int max_i;//Debug

int main() {
	//ifstream ifs("C:\\Users\\dell\\Downloads\\P1080_9.in", ios::in);//Debug
	int n, highest = 0;
	cin >> n;
	//ifs >> n;//Debug
	for (int i = 0; i <= n; i++)
		cin >> queue[i].left >> queue[i].right;
		//ifs >> queue[i].left >> queue[i].right;//Debug
	//ifs.close();//Debug
	sort(queue + 1, queue + n + 1, [](person p_1, person p_2) {
		return p_1.left * p_1.right < p_2.left * p_2.right;
		});

	for (int i = 1; i <= n; i++) {
		//if (i == 1000) system("pause");//Debug
		num[(i + 1) % 2][0] = int64_t(queue[i - 1].left) * num[i % 2][0];
		int j = 1;
		while (j <= highest + 1)
		{
			num[(i + 1) % 2][j] = int64_t(queue[i - 1].left) * num[i % 2][j];
			num[(i + 1) % 2][j] += num[(i + 1) % 2][j - 1] / mod_num;
			num[(i + 1) % 2][j - 1] %= mod_num;
			j++;
		}

		while (j > 0 && num[(i + 1) % 2][j] == 0) j--;
		highest = j;
		int z = j;
		int64_t upper = 0, lower = 0;
		while (z >= 0) {
			upper = (num[(i + 1) % 2][z] + lower) / int64_t(queue[i].right);
			lower = (num[(i + 1) % 2][z] + lower) % int64_t(queue[i].right) * mod_num;
			if (upper > max_num[z]) {
				max_len = j;
				//max_i = i;//Debug
				lower = 0;
				while (j >= 0) {
					upper = (num[(i + 1) % 2][j] + lower) / int64_t(queue[i].right);
					lower = (num[(i + 1) % 2][j] + lower) % int64_t(queue[i].right) * mod_num;
					max_num[j] = upper;
					j--;
				}
				break;
			}
			else if (upper == max_num[z]) {
				z--;
				continue;
			}
			else break;
		}
	}

	//cout << max_i << endl;//Debug
	cout << max_num[max_len];
	while (--max_len >= 0) cout << setiosflags(ios::right) << setfill('0') << setw(bits) << max_num[max_len];
}

我的高精度稍微改了一下,不想看的这段可以直接跳过

改动的第一个地方就是把10进制改成了1e10进制

这样好处就是可以把大臣手上的数看成一位(因为一定小于1e10),并减少循环次数

需要做出的改变就是模数由10变为1e10,在输出的时候要格式化一下

改动的第二个地方就是使用两个高精度数字进行高精度乘法(也就是%2的意义,i每改变奇偶性一次,乘积和乘数交换一次)

这样好处就是节约了空间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WitheredSakura_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值