题解 P2123 【皇后游戏】
前置链接
P1080 国王游戏(如果没过的话建议先过)
P2123 皇后游戏(本题)
大佬的题解(如果需要深入了解和更多知识)
一,目标:最后的大臣
这是一个困难的任务,但无论如何,皇后请我们去解决问题,我们应该全力以赴,说不定省下的钱可以分一点~~
让我们观察一下奖金的表达式:
有
这说明,排得越靠后的大臣得到的奖金越多。
由此可以知道得到奖金最多的大臣就是排最后的大臣,而皇后需要得到奖金最多的大臣的到的奖金尽量少,所以我们需要让排最后的大臣尽量少得奖金。
二,两个人间的选择
让我们从简单的情况开始吧。
现在假设一位不愿留下姓名的智者已经帮我们把前面的队伍排好了,只剩下两位大臣伊凡(Ivan)和杰克(Jack)在等待中了。我们就叫他们
两位大臣都想站在最后以获得最多的奖金,但皇后有她的心思。
当
| 大臣 | ... | |||||
|---|---|---|---|---|---|---|
| 左手 | ... | |||||
| 右手 | ... | |||||
| 奖金 | ... |
设
此时
如果将他们两个交换位置,那么同理我们可以得到我们将
(
现在我们比较一下
嗯。。。有些惨不忍睹。
在下一步处理前你要先看一下这几个式子(都很显然)
就这样我们得到了:
冷静一下,让我们来仔细观察一下这两个式子。
很容易发现两边都有(
那么设
有
看上去好多了,不是吗?
要比较两者,先关注不同的部分
我们请皇后叫一个计算师来计算一下这两个值。
“报告,经计算,这怎么算的,把计算师拖下去。。。)
那么,已知
其实有:
如何证明?
若
若
若
很好,梳理一下,由我们得知
通知皇后将伊凡排在杰克前,大功告成。
三,更深入的准则
刚才我们分析了最后两个人的情况,并得到了一个结论(即比较
对于任何一个大臣,改变他前方大臣的排序不会影响到
综上所述,我们可以将这个重要准则用于所有大臣,每一次交换位置都对我们的最终目标有利。让这个准则成为排序准则,得到一个大臣队伍序列,那应该就是答案。
但事情还没完。。。
四,不完整的准则
理想丰满,现实残酷。
皇后下命令让士兵给大臣排队,但在执行中,我们很快发现了问题:
如果
随便排吗?哦,那可会出问题的。
让我们请来
| 大臣 | 左手a | 右手b |
|---|---|---|
| 2 | 3 | |
| 3 | 4 | |
| 1 | 1 |
士兵先看了看
“好吧,你们俩就随便排吧。
士兵又看了看
“也是无所谓。
现在的情况(越向下越靠前):
| 大臣 | 左手a | 右手b | 奖赏c |
|---|---|---|---|
| 3 | 4 | 7 | |
| 1 | 1 | 8 | |
| 2 | 3 | 11 |
但,细心的你一定已经发现了:
这表明,
| 大臣 | 左手a | 右手b | 奖赏c |
|---|---|---|---|
| 2 | 3 | 5 | |
| 1 | 1 | 6 | |
| 3 | 4 | 10 |
看到这里,皇后已经打算把那个士兵扔到海里喂鱼了。但这并不是他的错,问题出在我们的排序准则上:
我们需要更完整的准则。
五,解决之道
让我们去采访一下伊凡(
“你对于这次排队的情况,有什么看法吗?”
“天哪,这完全是因为我左手上的数字太小,而右手上的太大了!”
就是这样,让我们再次观察一下那个重要准则:
从
证:
证明:
普通组:
如果一个大臣左手上的数字等于右手上的数字(
对于此组的大臣,无排序规则。
此组大臣任意两人间都无法用重要准则排序,因此虽然是随便排,但是不会出现之前一样的问题。
幸运儿组:
如果一个大臣左手上的数字大于右手上的数字(
对于此组的大臣,按右手上数字进行降序排序。
这样做也符合重要准则,证明类似倒霉蛋组。
组与组之间:
倒霉蛋组始终在前,普通组在中,幸运儿组始终在后。
这样做符合重要准则。
(假设
事实上就是:
(
(倒霉蛋组与普通组)
(幸运儿组与普通组)
证明类似于二中的
七,代码如下
五中的方法:
#include<iostream>
#include<algorithm>
using namespace std;
struct meo
{
int a,b;
}m[20010];//大臣们
bool cmp(meo x,meo y)
{
if (min(x.a,y.b)==min(x.b,y.a))
return x.a<y.a;//或是 return x.b>y.b;
else
return min(x.a,y.b)<min(x.b,y.a);
}
long long c[20010];
int main()
{
int T,n;
cin>>T;
long long sum;
for (int i=1;i<=T;++i)
{
cin>>n;
for (int j=1;j<=n;++j)
cin>>m[j].a>>m[j].b;
sort(m+1,m+n+1,cmp);//排序部分,再下为模拟部分
sum=0;
for (int j=1;j<=n;++j)
{
sum+=m[j].a;
c[j]=max(c[j-1],sum)+m[j].b;
}
cout<<c[n]<<endl;
}
return 0;
}
六中的方法:
#include<iostream>
#include<algorithm>
using namespace std;
struct meo
{
int a,b,d;//d表示组别,1=倒霉蛋,0=普通,-1=幸运儿
}m[20010];
bool cmp(meo x,meo y)
{
if (x.d!=y.d)
return x.d>y.d;
else if (x.d>0)
return x.a<y.a;
else
return x.b>y.b;
}
long long c[20010];
int main()
{
int T,n;
cin>>T;
long long sum;
for (int i=1;i<=T;++i)
{
cin>>n;
for (int j=1;j<=n;++j)
{
cin>>m[j].a>>m[j].b;
if (m[j].a<m[j].b)
m[j].d=1;//倒霉蛋组
else if (m[j].a>m[j].b)
m[j].d=-1;//幸运儿组
else
m[j].d=0;//普通组
/*
事实上这里写成
m[j].d=(m[j].b-m[j].a)/abs(m[j].b-m[j].a);
会更聪明
*/
}
sort(m+1,m+n+1,cmp);
sum=0;
for (int j=1;j<=n;++j)
{
sum+=m[j].a;
c[j]=max(c[j-1],sum)+m[j].b;
}
cout<<c[n]<<endl;
}
return 0;
}