[NOIP2012 提高组] 国王游戏

前言

题目传送门:https://www.luogu.com.cn/problem/P1080
依然是看题解会的,博主是飞舞。
找不到题解出处了,只记得在csdn上看的,果咩那塞原博主。

题目大意

\(1\)个国王和\(n\)个大臣,每个人左手和右手都写了一个数字。
国王站在队首,\(n\)个大臣排在后面。
每个大臣能获得的金币数为前面所有人左手数字的乘积除以本人右手数字(向下取整)。
问能获得最多金币的大臣的金币数最小为多少。

思路

题目分析

由分析可知,对于第\(i\)个大臣来说,前面的\(i-1\)个大臣的顺序无论怎么变化,只要这\(i-1\)个人是确定的,都不影响第\(i\)个大臣的金币数。

所以在分析时我们先假定前\(i-1\)个大臣的左手积是固定的,设为\(S\)
讨论第\(i\)\(i+1\)大臣的先后顺序
\(a_i\)为此时第\(i\)个大臣的左手的数,\(b_i\)为右手
那么第\(i\)个大臣的金币为\(S/b_i\)
\(i+1\)个大臣的金币为\(S \cdot a_i/b_{i+1}\)

若两人交换位置
则第\(i\)个位置的金币为\(S/b_{i+1}\)
\(i+1\)个位置为\(S\cdot a_{i+1}/b_i\)
若换位后第\(i+1\)位置的金币和原来比不减少,即换位前为更优状态

\[S \cdot a_i/b_{i+1} \leq S\cdot a_{i+1}/b_i \]

化简得

\[a_i \cdot b_i \leq a_{i+1}\cdot b_{i+1} \]

猜想金币递增?
即证明换位前

\[S/b_i \leq S \cdot a_i/b_{i+1} \]

\[b_{i+1} \leq a_i \cdot b_i \]

当然不是恒成立的,所以不能证明金币递增

注意事项

由数据范围\(1≤𝑛≤1,000,0<𝑎,𝑏<10000\)可知
最大的积可能为\(10000^{1000}=10^{4\times1000}=10^{4000}\)
所以明显要用到高精,具体到本题,就是高精除和高精乘

代码

能AC但是做法假

博主一开始以为能证明金币递增
但后来发现不能证明
虽然直观感受上排在最后的大臣的金币更多的概率更大
但还是可以构造出金币最大处不是末尾的数据的
所以不能只讨论最后一个大臣的金币

  • hack数据
2
21 1
2 3
2 7

还是把这份代码贴一下

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3f
#define ll long long
using namespace std;

ll n, a, b, cnt = 1; //cnt记录高精数的位数
ll x[4005] = {1}, y[4005]; //高精乘基础值为1

struct info {
	ll x;
	ll y;
	bool operator<(const info &o)const {
		return x * y < o.x * o.y;
	}
} e[1005];

void mtp(ll u);
void div(ll u);
void print();
void solve();

void mtp(ll u) { //高精乘
	ll temp = 0;
	for (ll i = 0; i < cnt; ++i) {
		x[i] *= u;
	}
	for (ll i = 0; i < cnt; ++i) {
		temp += x[i];
		x[i] = temp % 10;
		temp /= 10;
	}
	while (temp != 0) {
		x[cnt] = temp % 10;
		cnt++;
		temp /= 10;
	}
}

void div(ll u) { //高精除
	ll temp = 0;
	for (ll i = cnt - 1; i >= 0; i--) {
		temp = temp * 10 + x[i];
		y[i] = temp / u;
		temp %= u;
	}
}

void print() {
	ll temp = cnt;
	if (n == 1) { //特判只有一个大臣的情况
		cout << e[0].x / e[1].y << '\n';
	} else {
		while (y[temp] == 0) { //去掉前置0
			temp--;
			if (temp == -1) { //保证每个大臣至少会得到一个金币?
				cout<<1<<'\n'; //“所有的大臣都会获得国王奖赏的若干金币”
				return ; //感觉题目应该说的更明白一点
			}
		}
		for (ll i = temp; i >= 0; i--) { //打印高精数
			cout << y[i];
		}
	}
}

void solve() {
	cin >> n;
	for (ll i = 0; i <= n; i++) {
		cin >> e[i].x >> e[i].y; //读入国王和大臣
	}
	sort(e + 1, e + n + 1); //仅对大臣排序
	for (ll i = 0; i < n; i++) {
		mtp(e[i].x); //对前面的国王和n-1个大臣累乘
	}
	div(e[n].y); //已证明得最后的大臣的金币最大,所以只用看最后一个大臣的金币
	print();
}

int main() {
	ios_base::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);

	ll t = 1;
//	cin>>t;
	while (t--) {
		solve();
	}
	return 0;
}

luogu上题解区来自@EndSaH的代码

对高精用结构体进行了封装
原帖:@吴名玄 https://www.luogu.com.cn/problem/solution/P1080?page=2

#include<iostream>
#include<cstring>
#include<algorithm>//头文件
using namespace std;
struct num//用来实现高精度算法的结构体
{
    int len,s[10005];//len为数字位数,s数组为每一位上的数字
    num(int a=0)//构造函数
    {
        len=0;
        memset(s,0,sizeof(s));
        while(a)
        {
            s[++len]=a%10;
            a/=10;
        }
    }
	num operator*(const num &a) const//重载乘法运算(高精)
	{
		num c;int x;
    	for(int i=1;i<=a.len;i++)
    	{   
        	x=0;
    		for(int j=1;j<=len;j++)
        	{
    	        c.s[i+j-1]+=a.s[i]*s[j]+x;
	            x=c.s[i+j-1]/10;
            	c.s[i+j-1]%=10;
        	}
        	c.s[i+len]=x;
   		}
    	c.len=a.len+len;
    	while(!c.s[c.len]&&c.len!=1)
			c.len--;
	    return c;
	}
	num operator/(const int &a) const//重载整除运算(高精)
	{
    	num c;int x=0;
	    c.len=len;
    	for(int i=c.len;i>=1;i--)
	    {
        	x=x*10+s[i];
        	c.s[i]=x/a;
        	x%=a;
    	}
	    while(!c.s[c.len]&&c.len!=1)
        	c.len--;
    	return c;
	}
	bool operator<(const num &x) const//重载'<',max函数中要用
	{
	    if(len!=x.len)
        	return len<x.len;
    	for(int i=len;i>0;i--)
	        if(s[i]!=x.s[i]) 
        	    return s[i]<x.s[i];
    	return 0;
	}
};
struct human//表示大臣的结构体,a-左手,b-右手,c-乘积
{
    int a,b,c;
};
bool cmp(human x,human y)//用于快排
{
    return x.c<y.c;
}
int n;human p[10005];
int main()
{
    cin>>n;
    for(int i=0;i<=n;i++)
    {
        cin>>p[i].a>>p[i].b;
        p[i].c=p[i].a*p[i].b;
    }//读入并计算乘积
    num sum(p[0].a),ans;
    sort(p+1,p+n+1,cmp);//重新排序
    for(int i=1;i<=n;i++)
    {
        ans=max(ans,sum/p[i].b);//对每个大臣进行比较
        sum=sum*p[i].a;
    }
    for(int i=ans.len;i>=1;i--)
    	cout<<ans.s[i];//输出奖赏的最大值
    return 0;//一个完美的"return 0;"结束此题
}

收获

  • 之前没有高精度的板子,以这道题为契机补充了板子()
  • 自己给出了证明,并且hack掉了自己,很神奇
posted @ 2024-10-24 01:35  Gusare  阅读(141)  评论(0)    收藏  举报