[NOIP2012]国王游戏(贪心证明·邻项交换)

Problem

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

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

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

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

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

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

Sample Input
3
1 1
2 3
7 4
4 6
Sample Output
2
Hint
【输入输出样例说明】

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

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

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

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

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

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

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

【数据范围】

对于 20%的数据,有 1≤ n≤ 10,0 < a、b < 8;

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

对于 60%的数据,有 1≤ n≤100;

对于 60%的数据,保证答案不超过 109;

对于 100%的数据,有 1 ≤ n ≤1,000,0 < a、b < 10000。

Solution

个人认为这道题的难度非常大,不仅在于贪心的证明上还在于程序的实现上。

贪心证明:

设第 i i i位大臣的左边的数为 L [ i ] L[i] L[i],右边的数为 R [ i ] R[i] R[i];同样,第 i + 1 i+1 i+1个大臣左边和右边分别是 L [ i + 1 ] L[i+1] L[i+1] R [ i + 1 ] R[i+1] R[i+1]。设国王与前 i − 1 i-1 i1位大臣左边的数乘积= k k k,则第 i i i位大臣和第 i + 1 i+1 i+1位大臣最答案最优值的贡献是:
m a x ( k R [ i ]   ,   k ∗ L [ i ] R [ i + 1 ] ) max(\frac{k}{R[i]}\ ,\ \frac{k*L[i]}{R[i+1]}) max(R[i]k , R[i+1]kL[i])
化简,得:
m a x ( R [ i + 1 ] , L [ i ] ∗ R [ i ] ) max(R[i+1],L[i]*R[i]) max(R[i+1],L[i]R[i])
如果将i和i+1交换,就是:
m a x ( k R [ i + 1 ]   ,   k ∗ L [ i + 1 ] R [ i ] ) max(\frac{k}{R[i+1]}\ ,\ \frac{k*L[i+1]}{R[i]}) max(R[i+1]k , R[i]kL[i+1])
化简,得:
m a x ( R [ i ] , L [ i + 1 ] ∗ R [ i + 1 ] ) max(R[i],L[i+1]*R[i+1]) max(R[i],L[i+1]R[i+1])
显然,这两个人交换对于答案的变化与第 1 到 i − 1 1到i-1 1i1的大臣,第 i + 1 到 n i+1到n i+1n的大臣不造成任何影响。即只有这两项中的最大值可能会成为答案的最优值之一。则这一个具体的数值是:
m a x ( R [ i + 1 ] , L [ i ] ∗ R [ i ] , R [ i ] , L [ i + 1 ] ∗ R [ i + 1 ] ) max(R[i+1],L[i]*R[i],R[i],L[i+1]*R[i+1]) max(R[i+1],L[i]R[i],R[i],L[i+1]R[i+1])
因为L和R都是正整数,则有: R [ i + 1 ] < = L [ i + 1 ] ∗ R [ i + 1 ] , R [ i ] < = L [ i ] ∗ R [ i ] R[i+1]<=L[i+1]*R[i+1],R[i]<=L[i]*R[i] R[i+1]<=L[i+1]R[i+1],R[i]<=L[i]R[i]
因此这个具体的数值又可以转变为:
m a x ( L [ i ] ∗ R [ i ] , L [ i + 1 ] ∗ R [ i + 1 ] ) max(L[i]*R[i],L[i+1]*R[i+1]) max(L[i]R[i],L[i+1]R[i+1])
其中, L [ i ] ∗ R [ i ] L[i]*R[i] L[i]R[i]是交换前的数值, L [ i + 1 ] ∗ R [ i + 1 ] L[i+1]*R[i+1] L[i+1]R[i+1]是交换后的数值。

此时,我们可以得到下面这些结论:

  • 如果 L [ i ] ∗ R [ i ] < = L [ i + 1 ] ∗ R [ i + 1 ] L[i]*R[i]<=L[i+1]*R[i+1] L[i]R[i]<=L[i+1]R[i+1],则最优贡献是 L [ i + 1 ] ∗ R [ i + 1 ] L[i+1]*R[i+1] L[i+1]R[i+1],在交换后,此时不交换更优,更能取到最小值。
  • 如果 L [ i ] ∗ R [ i ] > = L [ i + 1 ] ∗ R [ i + 1 ] L[i]*R[i]>=L[i+1]*R[i+1] L[i]R[i]>=L[i+1]R[i+1],则最优贡献是 L [ i ] ∗ R [ i ] , L[i]*R[i], L[i]R[i],在交换前,此时交换更优,更能取到最小值。

此时我们希望的一个最优排序,使得无论如何交换都使结果不优,即最优结果一定在这个序列上,那么一定满足序列上的任何一个 L [ i ] ∗ R [ i ] < = L [ i + 1 ] ∗ R [ i + 1 ] L[i]*R[i]<=L[i+1]*R[i+1] L[i]R[i]<=L[i+1]R[i+1],因此我们得到了一个贪心策略:

  • 按照L[i]×R[i]为关键字从小到大排序。

然后就是处理结果的问题,显然结果要使用高精度来搞,高精度除法的搞法也不难理解:

模拟算式,首先要有一个数组a存储被除数,

用一个b存储除数,

还有用一个x存储剩下来的数.
[NOIP2012]国王游戏(贪心证明·邻项交换)_第1张图片

这是我初一写的高精度:

#include
using namespace std;
int main()
{
	string sa;
	int b,a[10000]={},x=0,i;
	cin>>sa>>b;
	for (i=1;i<=sa.size();i++)
	a[i]=sa[i-1]-48;//因为除法是从高位开始处理的,所以应该正的存入数组,当然这样更容易存储
	for (i=1;i<=sa.size();i++)
	{
		x=x*10+a[i];//求当前的余数x
		a[i]=x/b;//求第i位做出发所得到的结果
		x=x%b;//得到最后减完的余数
	}
	for (i=1;a[i]==0;i++);//以上为去掉前面的0
	for (int k=i;k<=sa.size();k++) cout<<a[k];//输出商
	cout<<endl<<x;//输出余数
	return 0;
}

现在得到代码:

#include 
using namespace std;

int n,A,B,Max=0;
int L[2000];
int R[2000];
int res[100000];
string ans="";
struct node {
	int Left,Right;
}a[2000];

bool cmp(node p1,node p2) 
{
	int c1=p1.Left*p1.Right;
	int c2=p2.Left*p2.Right;
	return c1<c2;
}

void Mul(int x)
{
	for (int i=1;i<=Max;++i) res[i]*=x;
	for (int i=1;i<=Max+10;++i)
	{
		res[i+1]+=res[i]/10;
		res[i]%=10;
		if (res[i] != 0) Max=i;
	}
}

void Div(int x)
{
	int i,y=0,k;
	int temp[100000];
	string s="";
	for (i=1;i<=Max;++i) temp[i]=res[Max-i+1];
	for (i=1;i<=Max;++i)
	{
		y=y*10+temp[i];
		temp[i]=y/x;
		y%=x; 
	} 
	for (i=1;i<=Max;++i)
	    if (temp[i] != 0) 
	    {
	    	for (int j=i;j<=Max;++j) s+=char(temp[j]+48);
	    	break;
		}
	if (s.size() > ans.size()) ans=s;
	else if (s.size() == ans.size() && s>ans) ans=s;
}

int main(void)
{
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	cin>>n>>A>>B;
	for (int i=1;i<=n;++i) 
		cin>>a[i].Left>>a[i].Right;
	sort(a+1,a+n+1,cmp);
	for (int i=1;i<=n;++i) 
	    L[i]=a[i].Left,R[i]=a[i].Right;
	L[0]=A,R[0]=B;
	res[1]=1,Max=1;
	for (int i=1;i<=n;++i) 
	{
		Mul(L[i-1]);
		Div(R[i]);
	}
	cout<<ans<<endl;
	return 0;
}

你可能感兴趣的:(排序·贪心·模拟,[算法进阶指南]习题题解)