算法
(贪心) $O(n^2)$
我们先给出做法,再证明其正确性。
做法:直接将所有大臣按左右手上的数的乘积从小到大排序,得到的序列就是最优排队方案。
证明:
我们记第 $i$ 个大臣左手上的数是 $A_i$,右手上的数是 $B_i$。
假设当前的排队方案不是按 $A_i * B_i$ 从小到大排序的,则一定存在某两个相邻的人,满足 $A_i * B_i > A_{i+1} * B_{i+1}$。
我们现在将这两个人的位置互换,然后考虑他们在交换前和交换后所获得的奖励是多少:
- 交换前:第 $i$ 个人是 $\frac{\prod_{j=0}^{i-1}A_j}{B_i}$,第 $i + 1$ 个人是 $\frac{\prod_{j=0}^{i}A_j}{B_{i + 1}}$;
- 交换后:第 $i$ 个人是 $\frac{\prod_{j=0}^{i-1}A_j}{B_{i + 1}}$,第 $i + 1$ 个人是 $\frac{A_{i + 1} * \prod_{j=0}^{i - 1}A_j}{B_i}$;
由于我们接下来只比较这四个数的大小关系,而且所有 $A_i, B_i$ 均大于0,所以可以将每个数除以 $\prod_{j=0}^{i-1}A_j$,然后乘 $B_i * B_{i + 1}$,得到:
第 $i$ 个人 | 第 $i + 1$ 个人 | |
---|---|---|
交换前 | $B_{i + 1}$ | $A_i * B_i$ |
交换后 | $B_i$ | $A_{i + 1} * B_{i + 1}$ |
由于 $A_i > 0$,所以 $B_i \le A_i * B_i$,并且 $A_i * B_i > A_{i+1} * B_{i+1}$,所以 $max(B_i, A_{i + 1} * B_{i + 1}) \le A_i * B_i \leq max(B_{i + 1}, A_i * B_i)$, 所以交换后两个数的最大值不大于交换前两个数的最大值。
而且交换相邻两个数不会对其他人的奖金产生影响,所以如果存在逆序,则将其交换,得到的结果一定不会比原来更差。
所以从小到大排好序的序列就是最优解,证毕。
时间复杂度
排序的时间复杂度是 $O(nlogn)$。
这道题目的时间复杂度瓶颈在高精度计算上,最坏情况下所有 $A_i = 9999$,则前 $i$ 个数的乘积大约是 $4i$ 位,每次乘一个新的数就需要 $4i$ 的计算量,所以总共的计算量是 $O(4 * \sum_{i = 1}^ni) = O(n^2)$。
C++ 代码
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<int, int> PII;
const int N = 1010;
int n;
PII p[N];
vector<int> mul(vector<int>a, int b)
{
vector<int> c;
int t = 0;
for (int i = 0; i < a.size(); i ++ )
{
t += a[i] * b;
c.push_back(t % 10);
t /= 10;
}
while (t)
{
c.push_back(t % 10);
t /= 10;
}
return c;
}
vector<int> div(vector<int>a, int b)
{
vector<int> c;
bool is_first = true;
for (int i = a.size() - 1, t = 0; i >= 0; i -- )
{
t = t * 10 + a[i];
int x = t / b;
if (!is_first || x)
{
is_first = false;
c.push_back(x);
}
t %= b;
}
reverse(c.begin(), c.end());
return c;
}
vector<int> max_vec(vector<int> a, vector<int> b)
{
if (a.size() > b.size()) return a;
if (a.size() < b.size()) return b;
if (vector<int>(a.rbegin(), a.rend()) > vector<int>(b.rbegin(), b.rend())) return a;
return b;
}
int main()
{
cin >> n;
for (int i = 0; i <= n; i ++ )
{
int a, b;
cin >> a >> b;
p[i] = {a * b, a};
}
sort(p + 1, p + n + 1);
vector<int> product(1, 1);
vector<int> res(1, 0);
for (int i = 0; i <= n; i ++ )
{
if (i) res = max_vec(res, div(product, p[i].first / p[i].second));
product = mul(product, p[i].second);
}
for (int i = res.size() - 1; i >= 0; i -- ) cout << res[i];
cout << endl;
return 0;
}
先看最后一个大臣,一旦这个大臣确定了,那么前面所有大臣的左手乘积可以确定,这个大臣的右手又是确定的。
这个大臣的得分是:
(所有大臣左手乘积 / 此大臣左手) / 此大臣右手
所以只需要最大化打成(此大臣左手 * 此大臣右手)的值即可。
最终的排序应该就是按照左手 * 右手进行从小到大排序即可。
新思路欸,感谢
很聪明的思路
这道题没让除去本人的左手
他的方法算上了自己的左手,所以要除去自己的左手
不太对,因为每个人的右手的数只和自己有关,所以不能贪心
我是这样想的,每个人左手影响的是他后面的所有人,而右手仅仅影响自己;
既然想尽可能减少花费,那么左手大的人就必须往后放,否则会导致他后面人的钱都变大;
同时,右手仅仅影响自己,既然想降低开销,那么就应该把右手大的人也往后放,因为越靠后前面大臣的左手累计的乘积越大,那么这只很大的右手在除的时候,发挥的作用也越大(相比放在前面,轻易的就把这个很大的除数浪费掉了);
因此,左右手这两个属性是同向的,都是越大越应该往后放,至于为什么是乘法而不是加法,因为本题就是乘除法计算
主要是严谨性
秀!
排在最后的人获得的奖励是最多,只要让最后的人获得的奖励最少就行了。按照这种方式提交代码也是可以通过的。
排在最后的人获得的奖励是最多 并不严谨
为什么不是,所以奖励固定,只需要随便换个右手最大的就行
不一定吧?
3
1000 1
10 1000
10 1
(赖皮数据)
写得真好,好详细,比洛谷题解区强多了
证明写得真好orz
建议压位(
本题的时间限制比较宽松,其实压不压都可以hh
捕捉抽风大佬%%%%%%%%%%%%
好妙啊qwq
试了一下,按左右手值之和排序也可以AC
tql
牛逼,woc
实际,我把按照两个数的乘法排序换成了按照两个数的加法排序也能过,然后我直接复制上面y总的代码并改为加法也能过,啊啊啊这是为啥啊
交换后第$i$个人是$\frac{\prod\limits_{j=0}^{i}A_j}{B_{i+1}}$,不是$\frac{\prod\limits_{j=0}^{i-1}A_j}{B_{i+1}}$
不对,搞错了,是第$i+1$个人的错了。。。
根据右手从小到到排序,如果右手相同,根据左手大小从小到到排序。过了70%的案例 。这个按左手确实想不到的
为啥老是直觉上来感觉,应当按左手大小排序,按乘积排序确实难想
左手和右手对收入都有影响的 所以只考虑一个的话不太周全 我上来就想到乘积了 是因为之前做过类似的
高精题目太恶心了
我在完全没用高精的时候60,用了高精过了一个点,调完后过了两个点qaq
y总感觉这里正序逆序绕的好乱呀,我试了下其实除法不逆序,得到的就是低位到高位的结果,然后比较函数也不需要逆序构造比较,然后最后输出也不需要逆序输出,也可以AC
也是可以的hh
看到高精度就放弃了
加油!
请教一下大佬,比较大小那里,我这样写return max(vector(int)(a.rbegin(), a.rend()), vector(int)(b.rbegin(), b.rend()));WA了,不知道为什么用max就WA了…
只有位数相同的时候可以比较字典序,否则位数长的数较大。
我先对长度进行判断了的
if (a.size() < b.size())return b;
if (a.size() > b.size())return a;
return max(vector(int)(a.rbegin(), a.rend()), vector(int)(b.rbegin(), b.rend()));
要返回原数组,不能返回翻转之后的数组。
哦,我傻了,谢谢大佬,写错了
请问大佬,一开始是怎么想到是按乘积排序的呢
灵感来自于AcWing 125. 耍杂技的牛,只是把加法换成了乘法。
按常理 左手的数越小 应该排在前面, 右手的数越小也应该排在前面 就试着乘一下 没想到蒙对了
第六感很强。
由于 Ai>0Ai>0,所以 Bi≤Ai∗BiBi≤Ai∗Bi,并且 Ai∗Bi>Ai+1∗Bi+1Ai∗Bi>Ai+1∗Bi+1,所以 max(Bi,Ai+1∗Bi+1)≤Ai∗Bi≤max(Bi+1,Ai∗Bi)max(Bi,Ai+1∗Bi+1)≤Ai∗Bi≤max(Bi+1,Ai∗Bi), 所以交换后两个数的最大值不小于交换前两个数的最大值。
这句话最后应该是应该吧不小于改成不大于吧
多谢指正hh 已修改~
%%%%%%%