几道贪心证明题,基本都是通过交换相邻元素来证明结论

对于有顺序的序列类问题,若要求最小最大值,常常可考虑贪心解决,常用套路就是对于一个排列来说,考虑交换其中的相邻两个元素可以使得结果更优,则通常能得到一个方案使得整个排列的结果最优。

leetcode1665

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>=m2p+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>=m1a1p>=m2a1a2p>=mna1a2an
然后考虑交换 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>=m2a2p>=m1a1a2
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=m1a1,p2=m2a1a2,p1=m2a2,p2=m1a1a2.
如果交换之后使得结果更优,则有
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 m2a2<m1a1,所以需要按照 m i − a i m_i-a_i miai进行排序, m i − a i m_i-a_i miai越小的越应该放在最后。

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 (aibi)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} (aibi)2=(a1b1)2+(a2b2)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} (aibi)2=(a1b2)2+(a2b2)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 a2b1a1b1+a1b2a2b2>0(a2a1)(b1b2)>0

  1. a 2 − a 1 > 0 a_2-a_1>0 a2a1>0,则 b 1 − b 2 > 0 b_1-b_2>0 b1b2>0,即 a 1 < a 2 , b 2 < b 1 a_1<a_2,b_2<b_1 a1<a2,b2<b1
  2. a 2 − a 1 < 0 a_2-a_1<0 a2a1<0,则 b 1 − b 2 < 0 b_1-b_2<0 b1b2<0,即 a 1 > a 2 , b 2 > b 1 a_1>a_2,b_2>b_1 a1>a2,b2>b1

结论也就是大的数要和大的数配对,小的数要和小的数配对,才能使得最终结果最小。
后续的解答请看解法
再附上其他的证明方法:排序不等式的三种证明方法

P1080 国王游戏

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 aibi从小到大排序即可。

解答

该题的难点主要在于高精度。

#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 s2w1应该越大越好,也即 s 2 − w 1 > = s 1 − w 2 s_2-w_1>=s_1-w_2 s2w1>=s1w2,得到 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} EiLiT+Ei+1Li+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+1Li+1T+EiLi(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;
	}
}

更多的练习

点我

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值