第六章贪心(四):推公式(AcWing 125:耍杂技的牛 && AcWing 114:国王游戏)
AcWing 125:耍杂技的牛
题目
农民约翰的 N 头奶牛(编号为 1…N)计划逃跑并加入马戏团,为此它们决定练习表演杂技。
奶牛们不是非常有创意,只提出了一个杂技表演:
叠罗汉,表演时,奶牛们站在彼此的身上,形成一个高高的垂直堆叠。
奶牛们正在试图找到自己在这个堆叠中应该所处的位置顺序。
这 N 头奶牛中的每一头都有着自己的重量 Wi 以及自己的强壮程度 Si。
一头牛支撑不住的可能性取决于它头上所有牛的总重量(不包括它自己)减去它的身体强壮程度的值,现在称该数值为风险值,风险值越大,这只牛撑不住的可能性越高。
您的任务是确定奶牛的排序,使得所有奶牛的风险值中的最大值尽可能的小。
输入格式
第一行输入整数 N,表示奶牛数量。
接下来 N 行,每行输入两个整数,表示牛的重量和强壮程度,第 i 行表示第 i 头牛的重量 Wi 以及它的强壮程度 Si。
输出格式
输出一个整数,表示最大风险值的最小可能值。
数据范围
1≤N≤50000,
1≤Wi≤10,000,
1≤Si≤1,000,000,000
解答
思路: 贪心策略,我们先分析每头牛的危险值 = 他前面牛的w(重量值)和 - 自身的s(强壮值),要使每头牛的危险值最小,这显然是与w 和 s同时相关,所以先想出一种做法按 每头牛的w + s进行升序排序(题见多了可能就会有这种题感)。证明如下:
牛 | 交换前 | 交换后 |
---|---|---|
i | ∑ j = 1 i − 1 w j − s i \sum_{j=1}^{i-1} w_j -s_i ∑j=1i−1wj−si | ∑ j = 1 i − 1 w j + w i + 1 − s i \sum_{j=1}^{i-1} w_j +w_{i+1}-s_i ∑j=1i−1wj+wi+1−si |
i+1 | ∑ j = 1 i w j − s i + 1 \sum_{j=1}^{i} w_j -s_{i+1} ∑j=1iwj−si+1 | ∑ j = 1 i − 1 w j − s i + 1 \sum_{j=1}^{i-1} w_j -s_{i+1} ∑j=1i−1wj−si+1 |
其他牛的危险值显然不变,所以分析交换前后这两头牛中最大的危险值即可。
将上述式子进行化简,每个式子减去
∑
j
=
1
i
−
1
w
j
\sum_{j=1}^{i-1} w_j
∑j=1i−1wj 得到如下式子。
牛 | 交换前 | 交换后 |
---|---|---|
i | − s i -s_i −si | w i + 1 − s i w_{i+1}-s_i wi+1−si |
i+1 | w i − s i + 1 w_i -s_{i+1} wi−si+1 | − s i + 1 -s_{i+1} −si+1 |
由于s, w都是正数,
w
i
−
s
i
+
1
>
−
s
i
+
1
w_i−s_{i+1}>−s_{i+1}
wi−si+1>−si+1,
w
i
+
1
−
s
i
>
−
s
i
w_{i+1}−s_i>−s_i
wi+1−si>−si
比较
w
i
−
s
i
+
1
w_i−s_{i+1}
wi−si+1,
w
i
+
1
−
s
i
w_{i+1}−s_i
wi+1−si即可
当 w i − s i + 1 > = w i + 1 − s i w_i−s_{i+1}>=w_{i+1}−s_i wi−si+1>=wi+1−si,即 w i + s i > = w i + 1 + s i + 1 w_i+s_i>=w_{i+1}+s_{i+1} wi+si>=wi+1+si+1时, 交换后更优
当 w i − s i + 1 < w i + 1 − s i w_i−s_{i+1}<w_{i+1}−s_i wi−si+1<wi+1−si,即 w i + s i < w i + 1 + s i + 1 w_i+s_i<w_{i+1}+s_{i+1} wi+si<wi+1+si+1时, 交换前更优
所以得到做法: 按每头牛的 w + s 进行排序, 当存在逆序时就进行交换(即升序排序),
然后根据题意算出每头牛的危险值记录其中的最大值即可。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 50010;
typedef pair<int, int> PII;
int main()
{
int n;
scanf("%d", &n);
PII cow[N];
for(int i=0; i<n; i++)
{
int w, s;
scanf("%d%d", &w, &s);
cow[i]={w+s, w};
}
// 按照w+s排序,sort对pair默认按first排序,first相同按second排序
sort(cow, cow+n);
// 依次计算每一头牛的风险值,找出最大风险值
int result = -2e9, w_sum = 0;
for(int i=0; i<n; i++)
{
int s=cow[i].first-cow[i].second, w=cow[i].second;
result = max(result, w_sum-s);
w_sum += w;
}
printf("%d\n", result);
return 0;
}
AcWing 114:国王游戏
题目
恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏。
首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、右手上各写一个整数。
然后,让这 n 位大臣排成一排,国王站在队伍的最前面。
排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:
排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。
国王不希望某一个大臣获得特别多的奖赏,所以他想请你帮他重新安排一下队伍的顺序,使得获得奖赏最多的大臣,所获奖赏尽可能的少。
注意,国王的位置始终在队伍的最前面。
输入格式
第一行包含一个整数 n,表示大臣的人数。
第二行包含两个整数 a 和 b,之间用一个空格隔开,分别表示国王左手和右手上的整数。
接下来 n 行,每行包含两个整数 a 和 b,之间用一个空格隔开,分别表示每个大臣左手和右手上的整数。
输出格式
输出只有一行,包含一个整数,表示重新排列后的队伍中获奖赏最多的大臣所获得的金币数。
解答
算法:贪心 + 高精度
与耍杂技的牛类似,只不过是加减法换为乘除法,可以证明当按 a*b 排列,获得最多的奖赏最小。
注意:国王始终在第一位置不变(因此按 a*b 排序时只排序臣子);注意高精度求解。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 1010;
vector<int> mul(vector<int> A, int b)
{
vector<int> C;
int t = 0;
for(int i = 0; i < A.size() || t; i++)
{
if(i < A.size()) t += A[i]*b;
C.push_back(t % 10);
t /= 10;
}
while(C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
vector<int> div(vector<int> A, int b)
{
int r = 0;
vector<int> C;
for(int i = A.size()-1; i >= 0; i--)
{
r = r*10 + A[i];
C.push_back(r / b);
r %= b;
}
reverse(C.begin(), C.end());
while(C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
vector<int> max_t(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()
{
int n;
scanf("%d", &n);
n += 1;
PII people[N];
for(int i=0; i<n; i++){
int a, b;
scanf("%d%d", &a, &b);
people[i] = {a*b, a};
}
sort(people+1, people + n);
vector<int> a_mul;
a_mul.push_back(1);
vector<int> result;
result.push_back(0);
// long int result = -2e9, a_mul = people[0].second;
for(int i=0; i<n; i++)
{
int a = people[i].second, b=people[i].first/people[i].second;
if(i) result = max_t(result, div(a_mul, b));
a_mul = mul(a_mul, a);
}
for(int i = result.size()-1; i >= 0; i--) cout << result[i];
// printf("%d\n", result);
return 0;
}