[USACO05NOV] 奶牛玩雜技
題目背景
Farmer John 養了 \(N\) 頭牛,她們已經按 \(1\sim N\) 依次編上了號。FJ 所不知道的是,他的所有牛都夢想著從農場逃走,去參加馬戲團的演出。可奶牛們很快發現她們那笨拙的蹄子根本無法在鋼絲或晃動的的鞦韆上站穩(她們還嘗試過把自己裝在大炮裡發射出去,但可想而知,結果是悲慘的) 。最終,她們決定練習一種最簡單的雜技:把所有牛都摞在一起, 比如說, 第一頭牛站在第二頭的身上, 同時第二頭牛又站在第三頭牛的身上...最底下的是第 \(N\) 頭牛。
題目描述
每頭牛都有自己的體重以及力量,編號為 \(i\) 的奶牛的體重為 \(W_i\),力量為 \(S_i\)。
當某頭牛身上站著另一些牛時它就會在一定程度上被壓扁,我們不妨把它被壓扁的程度叫做它的壓扁指數。對於任意的牛,她的壓扁指數等於摞在她上面的所有奶牛的總重(當然不包括她自己)減去它的力量。奶牛們按照一定的順序摞在一起後, 她們的總壓扁指數就是被壓得最扁的那頭奶牛的壓扁指數。
你的任務就是幫助奶牛們找出一個摞在一起的順序,使得總壓扁指數最小。
輸入格式
第一行一個整數 \(N\)。
接下來 \(N\) 行,每行兩個整數 \(W_i\) 和 \(S_i\)。
輸出格式
一行一個整數表示最小總壓扁指數。
樣例 #1
樣例輸入 #1
3
10 3
2 5
3 3
樣例輸出 #1
2
提示
對於 \(100\%\) 的資料,\(1 \le N \le 5\times 10^4\),\(1 \le W_i \le 10^4\),\(1 \le S_i \le 10^9\)。
題目連結:https://www.luogu.com.cn/problem/P1842
思路:
-
本題我們剛拿到題,我們可以先進行模擬,我們可以定義一個結構體,儲存奶牛的兩個性質,力量和重量。一開始並沒有什麼太好的思路去計算最小的壓扁指數,但是我們可以憑直覺猜想一下,我們要使得這一群奶牛當中,被壓得最扁的奶牛的壓扁指數儘可能的小,那麼我們是不是就是要讓最扁的那頭奶牛的上面的所有牛的體重之和儘可能的小,並且讓這頭牛的力量儘可能的大,對吧?
-
所以說我們最低的壓扁指數不僅僅與體重有關,也與牛的力量有關,所以說我們在採取排序策略的同時,我們不能夠僅僅根據體重或者是根據力量這一個元素來排序,我們一定要結合兩個屬性來排序,我們不妨猜想根據
體重和力量的某種組合方式
來進行排序,可以是重量與力量之差
來排序,也可以是力量與重量之和
來排序,只要我們證明,我們以某種排序規則下得到的總壓扁指數是最小的,那麼我們就成功證明了我們方案的可行性,對吧?
貪心策略的證明:
-
我們不妨先猜想按照力量和重量之和來排序,力量與重量之和小的排前面,反之則排後面。那麼我們就需要證明,在這種排序規則之下,我們得到的總壓扁指數就是最小的總壓扁指數我們假設第i頭牛它的壓扁指數是總壓扁指數,那麼它前面所有牛的總重量是\(w_1\)+\(w_2\)+....w(i-1),此時的總壓扁指數為
w1+w2+....+w(i-1)-si
,那麼我們如何去證明這個壓扁指數就是最小的呢?我們可以試著交換任意兩行奶牛,比如我們讓第i頭奶牛與第i-1頭奶牛交換,那麼此時的總壓扁指數就變成了w1+w2+...wi-s(i-1)
,我們只需要證明w1+w2+..w(i-1)-si<w1+w2+...+wi-s(i-1)
即可由於我們是按照體重與力量之和來升序排列的,那麼我們肯定就有w(i-1)+s(i-1)<wi+si
這個式子,觀察上述不等式,只有wi,w(i-1),si,s(i-1)四個地方不一樣,我們有w(i-1)+s(i-1)<wi+si
這個式子兩邊移項以後可以得到w(i-1)-si<wi-s(i-1)
,剛好是上面的式子變形得到的,那麼我們就成功證明了我們策略的可行性 -
其它的情況,例如按照重量與力量之差來升序排列,這種方式我們也是可以猜想的,我們也可也自行證明這個策略的可行性,如果我們能夠證明我們當前策略的辦法就是最優解,我們就可以採納,不過這道題在這種情況下,是不可取的,我們也可以自行證明一下。
程式碼:
#include<algorithm>
#include<iostream>
using namespace std;
const int N = 5e4 + 10;
//用結構體來儲存重量與力量兩個屬性
struct node {
int w;
int s;
}a[N];
int n;
//按照力量與重量之和來升序排列
bool compare(node A, node b) {
return A.w + A.s < b.w + b.s;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i].w >> a[i].s;
}
sort(a + 1, a + 1 + n, compare);
//答案先最隨便初始化為一個最小值
int res = -2e9;
int t = 0;
for (int i = 1; i <= n; i++) {
//結果取壓扁指數的最大值
res = max(res, t - a[i].s);
t += a[i].w;
}
cout << res;
return 0;
}
總結:
貪心策略很多時候都是根據我們的直覺來得到思路的,但是我們有時並不能像這道題一樣,給出嚴格的證明過程,但是我們當我們舉不出任何反例時,我們就可以試試我們的貪心策略了!
總而言之,貪心策略很多時候都是我們的常識告訴我們應該這麼做,只要我們舉不出任何的反例,我們就可以嘗試貪心策略,有時候我們也可以去嘗試嚴格的數學證明我們的策略是正確的!