对于有顺序的序列类问题,若要求最小最大值,常常可考虑贪心解决,常用套路就是对于一个排列来说,考虑交换其中的相邻两个元素可以使得结果更优,则通常能得到一个方案使得整个排列的结果最优。
leetcode1665
分析:
两种分析方法,一种是正向考虑,即设最小的初始能量是
p
p
p,一种是逆向考虑,即设最小的结束能量是
p
p
p,这里我们使用逆向考虑的方法。
假设我们当前的任务的排列是
[
a
1
,
m
1
]
,
[
a
2
,
m
2
]
,
.
.
.
,
[
a
n
,
m
n
]
[a_1,m_1],[a_2,m_2],...,[a_n,m_n]
[a1,m1],[a2,m2],...,[an,mn],其中
[
a
1
,
m
1
]
[a_1,m_1]
[a1,m1]是最后一个完成的任务,即逆向考虑,设最小的结束能量是
p
p
p,那么可以列出如下式子:
p
+
a
1
>
=
m
1
p
+
a
1
+
a
2
>
=
m
2
⋮
p
+
a
1
+
a
2
+
⋯
+
a
n
>
=
m
n
p+a_1 >=m_1\\ p+a_1+a_2>=m_2\\ \vdots\\ p+a_1+a_2+\cdots+a_n>=m_n
p+a1>=m1p+a1+a2>=m2⋮p+a1+a2+⋯+an>=mn
将与
p
p
p无关的都移到右边,得到:
p
>
=
m
1
−
a
1
p
>
=
m
2
−
a
1
−
a
2
⋮
p
>
=
m
n
−
a
1
−
a
2
−
⋯
−
a
n
p>=m_1-a_1\\ p>=m_2-a_1-a_2\\ \vdots\\ p>=m_n-a_1-a_2-\cdots-a_n
p>=m1−a1p>=m2−a1−a2⋮p>=mn−a1−a2−⋯−an
然后考虑交换
t
a
s
k
1
task_1
task1和
t
a
s
k
2
task_2
task2,得到:
p
>
=
m
2
−
a
2
p
>
=
m
1
−
a
1
−
a
2
p >=m_2-a_2\\ p >= m_1-a_1-a_2
p>=m2−a2p>=m1−a1−a2
令
p
1
=
m
1
−
a
1
,
p
2
=
m
2
−
a
1
−
a
2
,
p
1
′
=
m
2
−
a
2
,
p
2
′
=
m
1
−
a
1
−
a
2
.
p_1=m_1-a_1,p_2=m_2-a_1-a_2,p_1'=m_2-a_2,p_2'=m_1-a_1-a_2.
p1=m1−a1,p2=m2−a1−a2,p1′=m2−a2,p2′=m1−a1−a2.
如果交换之后使得结果更优,则有
m
a
x
(
p
1
′
,
p
2
′
)
<
m
a
x
(
p
1
,
p
2
)
max(p_1',p_2')<max(p_1,p_2)
max(p1′,p2′)<max(p1,p2)
又因为
p
2
′
<
p
1
&
&
p
2
<
p
1
′
p_2'<p_1 \&\&p_2<p_1'
p2′<p1&&p2<p1′,所以必有
p
1
′
<
p
1
p_1'<p_1
p1′<p1成立,即
m
2
−
a
2
<
m
1
−
a
1
m_2-a_2<m_1-a_1
m2−a2<m1−a1,所以需要按照
m
i
−
a
i
m_i-a_i
mi−ai进行排序,
m
i
−
a
i
m_i-a_i
mi−ai越小的越应该放在最后。
class Solution:
def minimumEffort(self, tasks: List[List[int]]) -> int:
tasks.sort(key = lambda x : x[1] - x[0])
res = 0
for ai, mi in tasks:
res = max(res + ai, mi)
return res
P1966 火柴排队
P1966 火柴排队
两个序列
a
,
b
a,b
a,b,可以交换各自的相邻元素,要使得
∑
(
a
i
−
b
i
)
2
\sum(a_i-b_i)^2
∑(ai−bi)2最小。
分析
假设当前排列为
[
a
1
,
a
2
,
.
.
.
,
a
n
]
[
b
1
,
b
2
,
.
.
.
,
b
n
]
[a_1,a_2,...,a_n]\\ [b_1,b_2,...,b_n]
[a1,a2,...,an][b1,b2,...,bn]
那么
∑
(
a
i
−
b
i
)
2
=
(
a
1
−
b
1
)
2
+
(
a
2
−
b
2
)
2
+
.
.
.
(1)
\sum(a_i-b_i)^2=\\ (a_1-b_1)^2+(a_2-b_2)^2+... \tag{1}
∑(ai−bi)2=(a1−b1)2+(a2−b2)2+...(1)
交换
b
b
b序列中的
b
1
,
b
2
b_1,b_2
b1,b2。
∑
(
a
i
−
b
i
)
2
=
(
a
1
−
b
2
)
2
+
(
a
2
−
b
2
)
2
+
.
.
.
(2)
\sum(a_i-b_i)^2=\\ (a_1-b_2)^2+(a_2-b_2)^2+... \tag{2}
∑(ai−bi)2=(a1−b2)2+(a2−b2)2+...(2)
假设交换后结果更小,那么
(
1
)
−
(
2
)
>
0
(1)-(2)>0
(1)−(2)>0,即
a
2
b
1
−
a
1
b
1
+
a
1
b
2
−
a
2
b
2
>
0
整
理
后
得
(
a
2
−
a
1
)
(
b
1
−
b
2
)
>
0
a_2b_1-a_1b_1+a_1b_2-a_2b_2>0\\ 整理后得\\ (a_2-a_1)(b_1-b_2)>0
a2b1−a1b1+a1b2−a2b2>0整理后得(a2−a1)(b1−b2)>0
- 若 a 2 − a 1 > 0 a_2-a_1>0 a2−a1>0,则 b 1 − b 2 > 0 b_1-b_2>0 b1−b2>0,即 a 1 < a 2 , b 2 < b 1 a_1<a_2,b_2<b_1 a1<a2,b2<b1
- 若 a 2 − a 1 < 0 a_2-a_1<0 a2−a1<0,则 b 1 − b 2 < 0 b_1-b_2<0 b1−b2<0,即 a 1 > a 2 , b 2 > b 1 a_1>a_2,b_2>b_1 a1>a2,b2>b1
结论也就是大的数要和大的数配对,小的数要和小的数配对,才能使得最终结果最小。
后续的解答请看解法。
再附上其他的证明方法:排序不等式的三种证明方法。
P1080 国王游戏
分析
分析方法同上,最终得到
第 i i i个人 | 第 i + 1 i+1 i+1个人 | |
---|---|---|
交换前 | b i + 1 b_{i+1} bi+1 | a i b i a_ib_i aibi |
交换后 | b i b_i bi | a i + 1 b i + 1 a_{i+1}b_{i+1} ai+1bi+1 |
由于 a i > 0 a_i>0 ai>0,所以 b i < a i b i b_i<a_ib_i bi<aibi, a i + 1 b i + 1 > b i + 1 a_{i+1}b_{i+1}>b_{i+1} ai+1bi+1>bi+1,所以要想满足 m a x ( b i + 1 , a i b i ) > m a x ( b i , a i + 1 , b i + 1 ) max(b_{i+1},a_ib_i)>max(b_i,a_{i+1},b_{i+1}) max(bi+1,aibi)>max(bi,ai+1,bi+1),必须有 a i b i > a i + 1 b i + 1 a_ib_i>a_{i+1}b_{i+1} aibi>ai+1bi+1,所以按照 a i ∗ b i a_i*b_i ai∗bi从小到大排序即可。
解答
该题的难点主要在于高精度。
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
#define x first
#define y second
const int N = 1e3 + 10;
pii p[N];
void mul(vector<int>& a, int b) {
int c = 0;
for (int i = 0; i < a.size(); i++) {
int t = a[i] * b + c;
a[i] = t % 10;
c = t / 10;
}
while (c) {
a.push_back(c % 10);
c /= 10;
}
}
vector<int> div(vector<int>& a, int b) {
vector<int> res;
bool is_first = true;
int t = 0;
for (int i = a.size()-1; i >= 0; i--) {
t = t*10 + a[i];
int x = t / b;
if (x || !is_first) {
res.push_back(x);
is_first = false;
}
t -= x * b;
}
reverse(res.begin(), res.end());
return res;
}
vector<int> max(vector<int> a, vector<int> b) {
if (a.size() > b.size()) return a;
else if (a.size() < b.size()) return b;
for (int i = a.size()-1; i >= 0; i--) {
if (a[i] > b[i]) return a;
else if (a[i] < b[i]) return b;
}
return a;
}
int main() {
int n;
cin >> n;
int a, b;
for (int i = 0; i <= n; i++) {
cin >> a >> b;
p[i] = {a * b, a};
}
sort(p+1, p+1+n);
vector<int> res, A = {1};
for (int i = 0; i <= n; i++) {
if (i) res = max(res, div(A, p[i].x / p[i].y));
mul(A, p[i].y);
}
for (int i = res.size()-1; i >= 0; i--) {
cout << res[i];
}
cout << endl;
}
AtCoder Educational DP Contest Problem X - Tower
AtCoder Educational DP Contest Problem X - Tower
分析
简单描述题意,从
n
n
n个物品中选出
k
k
k个物品堆叠起来,对第
i
i
i个物品来说,堆在它上面的物品要求满足
∑
j
=
i
+
1
k
w
[
j
]
<
=
s
[
i
]
\sum_{j=i+1}^k w[j]<= s[i]
∑j=i+1kw[j]<=s[i],求按照该条件堆出来的物品价值总和最大是多少。
首先还是按照某种顺序排列,那么得到:
s
1
>
=
w
2
+
.
.
.
+
w
k
s
2
>
=
w
3
+
.
.
.
+
w
k
s_1>=w_2+...+w_k\\ s_2>=w_3+...+w_k
s1>=w2+...+wks2>=w3+...+wk
交换
1
1
1与
2
2
2的顺序得到:
s
2
>
=
w
1
+
.
.
.
+
w
k
s
1
>
=
w
3
+
.
.
.
+
w
k
s_2>=w_1+...+w_k\\ s_1>=w_3+...+w_k
s2>=w1+...+wks1>=w3+...+wk
先不考虑价值多少,若想要后面的物品能放的更多,则显然交换后的
s
2
−
w
1
s_2-w_1
s2−w1应该越大越好,也即
s
2
−
w
1
>
=
s
1
−
w
2
s_2-w_1>=s_1-w_2
s2−w1>=s1−w2,得到
s
2
+
w
2
>
=
s
1
+
w
1
s_2+w_2>=s_1+w_1
s2+w2>=s1+w1,所以应该按照
s
+
w
s+w
s+w进行排序,将更大的
s
+
w
s+w
s+w放在底部。
又因为不是每一个物品都能被选择到最优结果中,所以每一个物品就有了选或不选两种选择,这不就是
01
01
01背包吗?将物品的
s
s
s作为其容量,将
w
w
w当做体积,
v
v
v是价值。
接下来用
01
01
01背包求解最优解即可。有一点需要注意的事项在代码中注释了。
#include <bits/stdc++.h>
using namespace std;
struct tower {
int w, s, v;
bool operator< (const tower& t) {
return s + w >= t.s + t.w;
}
};
const int N = 2e4 + 10;
typedef long long ll;
// f[i],重量为i时的最大价值
ll f[N];
tower a[N];
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> a[i].w >> a[i].s >> a[i].v;
}
sort(a, a + n);
// 后面选的物品能够任意放到到容量为小于[i].s的底部
for (int i = n-1; i >= 0; i--) {
// 注意这里背包的最大容量是w+s,相当于是每一个物品
// 都有一个自己的最大的背包容量的限制,也就是能堆在
// 它上面的物品的w的总和的限制
for (int j = a[i].w + a[i].s; j >= a[i].w; j--) {
f[j] = max(f[j], f[j-a[i].w] + a[i].v);
}
}
ll res = 0;
for (int i = 0; i < N; i++) {
res = max(res, f[i]);
}
cout << res << endl;
}
Kickstart
Kickstart 2019 Round B Problem B 能量石
分析
有
N
N
N块能量石,吃第
i
i
i个花费时间
S
i
S_i
Si秒,能量石会流失能量,第
i
i
i块能量石初始包含
E
i
E_i
Ei的能量,每秒失去
L
i
L_i
Li的能量,当开始吃时一块能量石时,立马获得全部能量,无论需要花费多少时间,能量最多降低为
0
0
0,请问吃能量石可获得的最大能量是多少?
首先不考虑能量石降为
0
0
0的情况,这时按照某种顺序来吃的话肯定能获得最大的能量。那么对于两个相邻的
i
,
i
+
1
i,i+1
i,i+1来说,假设在吃能量石
i
i
i之前已经消耗了时间
T
T
T,那么吃
i
,
i
+
1
i,i+1
i,i+1能获得能量:
E
i
−
L
i
∗
T
+
E
i
+
1
−
L
i
+
1
∗
(
T
+
S
i
)
(1)
E_i-L_i*T+E_{i+1}-L_{i+1}*(T+S_i)\tag{1}
Ei−Li∗T+Ei+1−Li+1∗(T+Si)(1)
交换顺序,吃
i
+
1
,
i
i+1,i
i+1,i能获得能量:
E
i
+
1
−
L
i
+
1
∗
T
+
E
i
−
L
i
∗
(
T
+
S
i
+
1
)
(2)
E_{i+1}-L_{i+1}*T+E_i-L_i*(T+S_{i+1})\tag{2}
Ei+1−Li+1∗T+Ei−Li∗(T+Si+1)(2)
假设交换后结果更优,则
(
2
)
−
(
1
)
>
0
(2)-(1)>0
(2)−(1)>0,即
L
i
+
1
S
i
>
L
i
S
i
+
1
L_{i+1}S_i>L_iS_{i+1}
Li+1Si>LiSi+1,所以若不考虑能量降为
0
0
0的情况,按照该条件进行排序,按顺序吃就能获取到最大能量。
但是现在有些能量石的能量是会降到0的,所以我们还需要考虑吃能量石所消耗的总时间
T
T
T,也就是用
d
p
[
i
]
dp[i]
dp[i]表示消耗时间为
i
i
i时所能获得的最大能量,每一个能量石有吃或不吃两种选择,这就又转换为了
01
01
01背包问题。
#include <bits/stdc++.h>
using namespace std;
struct stone {
int s, e, l;
bool operator < (stone& r) {
return s * r.l < l * r.s;
}
};
const int N = 1e4 + 10;
const int inf = -1e8;
int main() {
int t, tt = 0;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<stone> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i].s >> a[i].e >> a[i].l;
}
sort(a.begin(), a.end());
vector<int> f(N, inf);
f[0] = 0;
for (int i = 0; i < n; i++) {
for (int j = N-1; j >= a[i].s; j--) {
f[j] = max(f[j], f[j-a[i].s] + max(0, a[i].e - (j-a[i].s)*a[i].l));
}
}
int res = 0;
for (int i = 0; i < N; i++) {
res = max(res, f[i]);
}
cout << "Case #" << ++tt << ": ";
cout << res << endl;
}
}