如何在一個陣列中找到是否存在數字 A
如何在一個陣列中找到是否存在數字 A
掃一遍 - > O(n)
如何在一個陣列中找到是否存在數字 A
掃一遍 - > O(n)
如果陣列長度 1000000
詢問次數 1000000呢
如何在一個陣列中找到是否存在數字 A
掃一遍 - > O(n)
如果陣列長度 1000000
詢問次數 1000000呢
1000000*1000000 -> TLE
如果我對原陣列做一些預處理 ?
如果我對原陣列做一些預處理 ?
sort !
如果我對原陣列做一些預處理 ?
sort !
如果一個陣列是排序過的
如何找到 X 是否存在陣列中?
如果我對原陣列做一些預處理 ?
sort !
如果一個陣列是排序過的
如何找到 X 是否存在陣列中?
二分搜 !!
1 3 5 6 8 12 15 45 50 51
1 3 5 6 8 12 15 45 50 51
詢問 50
我可以找到第一個大於等於50的數字
如果等於50就代表有50
1 3 5 6 8 12 15 45 50 51
詢問 50
1 3 5 6 8 12 15 45 50 51
12 < 50
1 3 5 6 8 12 15 45 50 51
詢問 50
1 3 5 6 8 12 15 45 50 51
1 3 5 6 8 12 15 45 50 51
詢問 50
1 3 5 6 8 12 15 45 50 51
45 < 50
1 3 5 6 8 12 15 45 50 51
詢問 50
1 3 5 6 8 12 15 45 50 51
45 < 50
1 3 5 6 8 12 15 45 50 51
詢問 50
1 3 5 6 8 12 15 45 50 51
50 = 50
找到了 !
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define maxn 200005
main(){
int n,arr[maxn],q;
cin>>n;
for(int i=1;i<=n;++i) cin>>arr[i];
sort(arr+1,arr+1+n);
while(cin>>q){
int l = 0,r = n+1,mid;
if(arr[n] < q){
cout<<"No"<<endl;
continue;
}
while(l+1<r){
mid = (l+r)/2;
if(arr[mid] >= q) r = mid;
else l = mid;
}
if(arr[r] == q) cout<<"yes : "<<r<<endl;
else cout<<"No"<<endl;
}
}
二分搜只能拿來找數字 ?
二分搜只能拿來找數字 ?
其實只要有單調性的東西都可以二分搜
換個想法
1 3 5 6 8 12 15 45 50 51
找第一個大於等於45的元素
換個想法
1 3 5 6 8 12 15 45 50 51
找第一個大於等於45的元素
可以把陣列寫成 :
0 0 0 0 0 0 0 1 1 1
換個想法
1 3 5 6 8 12 15 45 50 51
找第一個大於等於45的元素
可以把陣列寫成 :
0 0 0 0 0 0 0 1 1 1
0 : < 45
1 : >= 45
那麼第一個大於等於45的
就是第一個 1
那麼第一個大於等於45的
就是第一個 1
0 0 0 0 0 0 0 1 1 1
那麼第一個大於等於45的
就是第一個 1
0 0 0 0 0 0 0 1 1 1
可以發現這個陣列也有單調的性質 !!
那麼第一個大於等於45的
就是第一個 1
0 0 0 0 0 0 0 1 1 1
可以發現這個陣列也有單調的性質 !!
可以用 O(logn) 找到 !!
所以只要東西或結果可以轉換成
0 0 0 0 0 0 0 1 1 1
像這樣有單調性的01陣列
所以只要東西或結果可以轉換成
0 0 0 0 0 0 0 1 1 1
像這樣有單調性的01陣列
就可以用二分搜找到第一個1、最後一個0
直接看例題比較快
給你一大堆字串、一值k,
要把他們排成不超過k行
求滿足以下條件時的最小寬度需求
條件 :
一個字串不能被切斷
每行第一個不為空
ex :
k = 4
abcdefg hij kl mn
abcdefg
hij
kl mn
這是這題的其中一解的排法
滿足不超過k行
ex :
k = 4
abcdefg hij kl mn
abcdefg
hij
kl mn
這是這題的其中一解的排法
滿足不超過k行
寬度需求為 8 (abcdefg+' ')
ex :
4
your smile illuminates like the sun... shion... suki, - same
your smile
illuminates like
the sun... shion...
suki, - same
其中一解得排法,寬度需求為20
where is 單調性 ?
where is 單調性 ?
假設把寬度需求從小到大排,
1 為可以排入所有單字
否則為 0
where is 單調性 ?
假設把寬度需求從小到大排,
1 為可以排入所有單字
否則為 0
寬度小
寬度大
00000000000011111111111111
where is 單調性 ?
假設把寬度需求從小到大排,
1 為可以排入所有單字
否則為 0
寬度小
寬度大
00000000000011111111111111
有單調性 !
假設有一台機器,你問他
每行寬度為 T 是否可以排入(合法狀態下)
他會回答 1 or 0
假設有一台機器,你問他
每行寬度為 T 是否可以排入(合法狀態下)
他會回答 1 or 0
就可以跟猜數字一樣,用二分搜找到第一個1
接下來就剩如何做出這台機器
接下來就剩如何做出這台機器
因為已經知道每行的寬度
因此我可以放到這個字放不下,
再換行
接下來就剩如何做出這台機器
因為已經知道每行的寬度
因此我可以放到這個字放不下,
再換行
最後看總行數有沒有大於k
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define maxn 200005
int K,n;
int arr[maxn];
bool chack(int T){
int cnt = 1,tmp = 0;
for(int i=1;i<=n;++i){
if(arr[i] > T) return false;
if(tmp + arr[i] > T){
cnt++;
tmp = arr[i];
if(cnt > K) return false;
}
else tmp += arr[i];
}
return cnt <= K;
}
main(){
ios::sync_with_stdio(0); cin.tie(0);
cin>>K;
string s;
while(cin>>s && !cin.eof()){
arr[++n] = s.size();
}
for(int i=1;i<n;++i) arr[i]++;
int l = 0,r = 1000006,mid;
while(l+1<r){
mid = (l+r)/2;
if(chack(mid)) r = mid;
else l = mid;
}
cout<<r<<endl;
}暑訓講過的好題
輸入一個n,問所有n*n的乘法表中,中位數是誰
n <= 1e6
把整張乘法表列出來,再排序找中位數
把整張乘法表列出來,再排序找中位數
1e12 * log(1e12)
TLE
單調性在哪裡 ?
假設我猜答案是k?
單調性在哪裡 ?
假設我猜答案是k?
k小
k大
00000000000011111111111111
1 : 表中 >= k 的數字數量大於等於 (n*n)/2
單調性在哪裡 ?
假設我猜答案是k?
k小
k大
00000000000011111111111111
1 : 表中 >= k 的數字數量大於等於 (n*n)/2
答案為第一個 1
如何計算表中大於等於k的數字數量k
1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
4 8 12 16 20
5 10 15 20 25
如何計算表中大於等於k的數字數量
1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
4 8 12 16 20
5 10 15 20 25
第 i 直排 : 1i 2i 3i 4i 5i 6i
k/i 取下高斯就是大於等於k的數字數量
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
int cal(int x){
int sum = 0;
for(int i=1;i<=n;++i) sum += min(x/i,n);
return sum;
}
main(){
cin>>n;
if(n<=3){
cout<<n<<endl;
return 0;
}
int l = 1,r = n*n, m, tar = (n*n+1)/2;
while(l+1<r){
m = (l+r)/2;
if(cal(m)>=tar) r = m;
else l = m;
}
cout<<r<<endl;
return 0;
}
什麼是分治 ?
什麼是分治 ?
把東西切一半然後遞迴下去做
什麼是分治 ?
把東西切一半然後遞迴下去做
把問題分成小問題,再把小問題的答案合併
什麼是分治 ?
把東西切一半然後遞迴下去做
把問題分成小問題,再把小問題的答案合併
「在 conquer 時能夠省去維護儲存所有資訊的力氣,只需要專心處理跨分隔線的資訊」,就是分治的精髓
分治步驟
分治步驟
分 : 把大問題拆成小問題
分治步驟
分 : 把大問題拆成小問題
邊界 : 問題分到可以直接解決的時候,直接算答案
分治步驟
分 : 把大問題拆成小問題
邊界 : 問題分到可以直接解決的時候,直接算答案
治 : 合併小問題的答案,得到大問題的答案
例題1
最大連續和
例題1
最大連續和
給一個包含n個數字的陣列
問一個連續區間的最大和是多少
例題1
最大連續和
給一個包含n個數字的陣列
問一個連續區間的最大和是多少
ex :
3 5 -7 3 2 -8 -9 6 6
as : 12
我要寫一個函式 f ( l , r )
目標讓他回傳 l ~ r 的答案 (最大連續和)
如何分,如何治?
如何分?
把陣列切成兩半
如何分?
把陣列切成兩半
如何分?
把陣列切成兩半
答案可能在哪裡 ?
如何分?
把陣列切成兩半
答案可能在哪裡 ?
1 : 全部左邊
如何分?
把陣列切成兩半
答案可能在哪裡 ?
1 : 全部左邊
2 : 全部右邊
如何分?
把陣列切成兩半
答案可能在哪裡 ?
1 : 全部左邊
2 : 全部右邊
3 : 跨越中間
如何治?
如何治?
如果答案全在左邊or右邊
如何治?
如果答案全在左邊or右邊
答案就是 max( f(l,mid), f(mid,r) )
如何治?
如果答案跨越中間 (mid) ?
如何治?
如果答案跨越中間 (mid) ?
跨越中間的答案一定是最大的藍色 + 綠色
綜合三種答案的可能
f (l,r) =
綜合三種答案的可能
f (l,r) =
最後邊界情況就是
if l == r
return arr[l]
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define maxn 200005
#define inf 1e18
int n,arr[maxn];
int f(int l,int r){
if(l==r) return arr[l];
int mid = (l+r)/2;
int res = max(f(l,mid),f(mid+1,r));
int R = 0,rmax = -inf, L = 0, lmax = -inf;
for(int i=mid+1;i<=r;++i){
R += arr[i];
rmax = max(rmax,R);
}
for(int i=mid;i>=l;--i){
L += arr[i];
lmax = max(lmax,L);
}
res = max(res,lmax+rmax);
return res;
}
main(){
cin>>n;
for(int i=1;i<=n;++i) cin>>arr[i];
cout<<f(1,n)<<endl;
}
很重要的點就是
你再 f 裡面 cal f 時,要相信他是對的,
這樣他就會是對的
例題 2 : merge sort
merge sort 很適合來理解分治概念
例題 2 : merge sort
merge sort 很適合來理解分治概念
一樣先想像
我要寫出一個函式,他不會回傳東西
但是他會幫我把 l,r 區間排序好
記為 g (l,r)
如何分 ?
如何分 ?
一樣分成兩半
這時我可以呼叫f (l,mid) , f(mid,r)
如何分 ?
一樣分成兩半
這時我可以呼叫f (l,mid) , f(mid,r)
因此現在左右區間都各別排序好了
我要做得只剩下讓整個區間排序好
我要做得只剩下讓整個區間排序好
3 6 9 13
5 7 10 11
3 5 6 7 9 10 11 13
作法就是每次比較兩個陣列的頭 (最小的)
把比較小的丟到新陣列裡,並刪掉
Text
void merge(int arr[], int l, int m, int r) {
int n1 = m - l + 1, n2 = r - m;
int L[n1 + 1], R[n2 + 1];
for (int i = 1; i <= n1; i++) L[i] = arr[l + i - 1];
for (int i = 1; i <= n2; i++) R[i] = arr[m + i];
int i = 1, j = 1, k = l;
while (i <= n1 && j <= n2) arr[k++] = (L[i] <= R[j]) ? L[i++] : R[j++];
while (i <= n1) arr[k++] = L[i++];
while (j <= n2) arr[k++] = R[j++];
}
void mergeSort(int arr[], int l, int r) {
if (l >= r) return;
int m = l + (r - l) / 2;
mergeSort(arr, l, m);
mergeSort(arr, m + 1, r);
merge(arr, l, m, r);
}
int main() {
int n; cin>>n;
int arr[n+1];
for(int i=1;i<=n;++i) cin>>arr[i];
mergeSort(arr, 1, n);
for(int i=1;i<=n;++i) cout<<arr[i]<<' ';
return 0;
}例題3 : 逆序數對
給一個長度為 n 的陣列
求數對 (i,j)滿足下條件
1 <= i < j <=n
arr[i] > arr[j]
ex :
2 6 6 1 9 4 3
as = 10
ex :
2 6 6 1 9 4 3
as = 10
一樣設 h(l,r) 會回傳 l~r中的逆序數對
分 : 把陣列分成兩半
分 : 把陣列分成兩半
如果數對中兩個都在左邊或右邊
那我可以直接呼叫 h(l,mid) 、h(mid,r)
再相加得到
因此只要考慮 i,j 在不同邊的數對數
所以對於每個左半邊的 i
要找有幾個右邊的 j
符合 arr[i] > arr[j]
因此只要考慮 i,j 在不同邊的數對數
所以對於每個左半邊的 i
要找有幾個右邊的 j
符合 arr[i] > arr[j]
排序 !
回想剛剛把兩個排序好的陣列合併的過程
回想剛剛把兩個排序好的陣列合併的過程
如果一個左邊的數字現在正被丟出來
(比較到他,然後他比較小)
回想剛剛把兩個排序好的陣列合併的過程
如果一個左邊的數字現在正被丟出來
(比較到他,然後他比較小)
10 12 15
5 8 9
回想剛剛把兩個排序好的陣列合併的過程
如果一個左邊的數字現在正被丟出來
(比較到他,然後他比較小)
10 12 15
8 9
5
回想剛剛把兩個排序好的陣列合併的過程
如果一個右邊的數字現在正被丟出來
(比較到他,然後他比較小)
10 12 15
8 9
5
那是不是代表左邊剩下的全部數字都比他大
逆序數對加上左邊剩餘數量
發現這個過程跟merge sort幾乎一模一樣!!
long long merge(int arr[], int l, int m, int r) {
int n1 = m - l + 1, n2 = r - m;
int L[n1], R[n2];
for (int i = 0; i < n1; i++) L[i] = arr[l + i];
for (int i = 0; i < n2; i++) R[i] = arr[m + 1 + i];
int i = 0, j = 0, k = l;
long long inv_count = 0;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k++] = L[i++];
} else {
arr[k++] = R[j++];
inv_count += n1 - i;
}
}
while (i < n1) arr[k++] = L[i++];
while (j < n2) arr[k++] = R[j++];
return inv_count;
}
long long mergeSort(int arr[], int l, int r) {
long long inv_count = 0;
if (l < r) {
int m = l + (r - l) / 2;
inv_count += mergeSort(arr, l, m);
inv_count += mergeSort(arr, m + 1, r);
inv_count += merge(arr, l, m, r);
}
return inv_count;
}
int main() {
int n;
cin >> n;
int arr[n];
for (int i = 0; i < n; ++i) cin >> arr[i];
cout<<mergeSort(arr, 0, n - 1)<<endl;
return 0;
}每次都選對自己最有利的選擇!
每次都選對自己最有利的選擇!
ex :
有 1、5、10元三種面額的硬幣
我要如何使用最少的硬幣來組出 x 元 ?
每次都選對自己最有利的選擇!
ex :
有 1、5、10元三種面額的硬幣
我要如何使用最少的硬幣來組出 x 元 ?
每次都選最還可以選的面額中最大的!
每次都選對自己最有利的選擇!
ex :
有 1、5、10元三種面額的硬幣
我要如何使用最少的硬幣來組出 x 元 ?
每次都選最還可以選的面額中最大的!
28 = 10 + 10 + 5 + 1 + 1 + 1
用了6枚硬幣
一定是對的嗎 ?
一定是對的嗎 ?
取決於有哪些面額,但這個情況下一定對
一定是對的嗎 ?
取決於有哪些面額,但這個情況下一定對
反例 :
有1、5、7三種面額
要組出 11 元
一定是對的嗎 ?
取決於有哪些面額,但這個情況下一定對
反例 :
有1、5、7三種面額
要組出 11 元
greedy策略 : 7+1+1+1+1
最佳解 : 5+5+1
從上面的例子可以發現,
greedy策略在有些情況可以找出最佳解,有些則不行
那我們要如何證明呢?
其實要非常嚴謹的證明是否可以用greedy
是很難的
我們常常會選擇比較簡單的證明
如何證明 ?
我們通常會假設一個不是用greedy策略
找到的最佳解 S (存在某個決策不是greedy)
如何證明 ?
接著我們將 S 的某個不是greedy的決策
換成 greedy
我們通常會假設一個不是用greedy策略
找到的最佳解 S (存在某個決策不是greedy)
如何證明 ?
我們通常會假設一個不是用greedy策略
找到的最佳解 S (存在某個決策不是greedy)
接著我們將 S 的某個不是greedy的決策
換成 greedy
發現經過這次交換後不會更差
證明以下題目可以通過每次取最大得到答案 :
有 1、5、10元三種面額的硬幣
我要如何使用最少的硬幣來組出 x 元 ?
證明以下題目可以通過每次取最大得到答案 :
有 1、5、10元三種面額的硬幣
我要如何使用最少的硬幣來組出 x 元 ?
假設有一個不是用這個策略得到的解 S
S 中選10元硬幣的數量 < greedy 解中的數量
證明以下題目可以通過每次取最大得到答案 :
有 1、5、10元三種面額的硬幣
我要如何使用最少的硬幣來組出 x 元 ?
假設有一個不是用這個策略得到的解 S
S 中選10元硬幣的數量 < greedy 解中的數量
那麼我們一定可以將一些S解中
所選的 1元、5元湊一湊,換成10元
證明以下題目可以通過每次取最大得到答案 :
有 1、5、10元三種面額的硬幣
我要如何使用最少的硬幣來組出 x 元 ?
假設有一個不是用這個策略得到的解 S
S 中選10元硬幣的數量 < greedy 解中的數量
那麼我們一定可以將一些S解中
所選的 1元、5元湊一湊,換成10元
並且這樣所花費的硬幣數量一定不會更多
可以拿剛剛的反例 1、5、7元的來試試
可以拿剛剛的反例 1、5、7元的來試試
那麼我們一定可以將一些S解中
所選的 1元、5元湊一湊,換成7元
發現剛剛的這步不一定會成立
有n個活動,每個活動開始/結束時間分別為 a、b
現在要選出最多的活動,使得任兩活動時間不衝突
ex :
要用什麼策略greedy的選呢?
要用什麼策略greedy的選呢?
優先選擇目前可參加的活動中
結束時間最早的 !
假設有一個不是貪心法的最優解 S
(存在某個選擇不是結束時間最早的)
假設有一個不是貪心法的最優解 S
(存在某個選擇不是結束時間最早的)
那麼我們可以將那個選擇換成
當時結束時間最早的活動
假設有一個不是貪心法的最優解 S
(存在某個選擇不是結束時間最早的)
那麼我們可以將那個選擇換成
當時結束時間最早的活動
這樣不僅參加數量步減少,還會騰出更多空間給後面的活動
這樣不僅參加數量步減少,還會騰出更多空間給後面的活動
這樣不僅參加數量步減少,還會騰出更多空間給後面的活動
#include<bits/stdc++.h>
using namespace std;
int n,as,r;
struct movie{
int s,t;
};
bool comp(movie a,movie b){
return b.t > a.t;
}
vector<movie> p;
int main(){
cin>>n;
for(int i=0;i<n;++i){
int a,b; cin>>a>>b;
p.push_back({a,b});
}
sort(p.begin(),p.end(),comp);
as =1;
r = p[0].t;
for(int i=1;i<n;++i){
if(r>p[i].s)continue;
r = p[i].t;
as++;
}
cout<<as;
return 0;
}
有n件工作要完成,每件工作的所需間為 t_i
,懲罰指數為 s_i,假設這件工作在單位時間 f 完成,
則會有懲罰 s_i * f
找到最小的懲罰總和
有n件工作要完成,每件工作的所需間為 t_i
,懲罰指數為 s_i,假設這件工作在單位時間 f 完成,
則會有懲罰 s_i * f
找到最小的懲罰總和
有四件工作,花費時間分別是 t=(1,4,5,6),而懲罰指數是 s=(1,3,4,7)。
EX :
有四件工作,花費時間分別是 t=(1,4,5,6),而懲罰指數是 s=(1,3,4,7)。
如果依編號順序(1,2,3,4),完工時間是(1,5,10,16),
扣款總額是
1*1 + 5*3 + 10*4 + 16*7 = 168。
EX :
有四件工作,花費時間分別是 t=(1,4,5,6),而懲罰指數是 s=(1,3,4,7)。
如果依編號順序(1,2,3,4),完工時間是(1,5,10,16),
扣款總額是
1*1 + 5*3 + 10*4 + 16*7 = 168。
如果依編號順序(4,1,3,2) ,完工時間是(6,7,12,16),
扣款總額是
6*7 + 7*1 + 12*4 + 16*3 = 145。
EX :
我們可以想想看要如何安排處理工作的排列順序
來讓懲罰最小
假設有一個不是greedy的最佳解排列 S
S_i 代表第 i 個是做哪個工作
假設有一個不是greedy的最佳解排列 S
S_i 代表第 i 個是做哪個工作
我們觀察相鄰兩項工作 a、b,
令 T 為之前所花費的總時間
並考慮兩種情況 :
1 : 不交換 (a -> b)
2 : 交換 (b -> a)
不交換 (先做a) 所產生的懲罰
不交換 (先做a) 所產生的懲罰
交換 (先做b) 所產生的懲罰
不交換 (先做a) 所產生的懲罰
交換 (先做b) 所產生的懲罰
不交換 (先做a) 所產生的懲罰
交換 (先做b) 所產生的懲罰
因此只要比較
誰較小即可
給定多組測資,每組包含 NNN 個人,每個人有兩個數值:
CiC_iCi:第 iii 人的餐點烹煮時間
EiE_iEi:第 iii 人的用餐時間
所有餐點需依序單獨烹煮,煮好立即上桌並開始用餐。每個人吃完的時間為他餐點被煮好時的時間 + EiE_iEi。你要決定煮餐順序,
使得所有人都吃完的時間最早
要什麼東西greedy呢 ?
要什麼東西greedy呢 ?
發現其實烹飪時間多久其實不太重要?
因此把吃飯時間越長的排越前面呢 ?
假設有一個最佳解的排列 S
裡面包含了某相鄰兩項 a , b
不符合greedy
即 b 的吃飯時間比 a 快
假設有一個最佳解的排列 S
裡面包含了某相鄰兩項 a , b
不符合greedy
即 b 的吃飯時間比 a 快
假設烹飪時間為 a_c、b_c
吃飯時間為 a_e、b_e
之前所花費的烹飪時間總和為 T
假設有一個最佳解的排列 S
裡面包含了某相鄰兩項 a , b
不符合greedy
即 b 的吃飯時間比 a 快
假設烹飪時間為 a_c、b_c
吃飯時間為 a_e、b_e
之前所花費的烹飪時間總和為 T
考慮交換、不交換
假設烹飪時間為 a_c、b_c
吃飯時間為 a_e、b_e
不交換 :
a 吃完的時間 :
b 吃完的時間 :
交換 :
a 吃完的時間 :
b 吃完的時間 :
不交換 :
a 吃完的時間 :
b 吃完的時間 :
交換 :
a 吃完的時間 :
b 吃完的時間 :
如果
不交換 :
a 吃完的時間 :
b 吃完的時間 :
交換 :
a 吃完的時間 :
b 吃完的時間 :
如果
則上面打勾的式子會是最大的,
代表不交換較好
不交換 :
a 吃完的時間 :
b 吃完的時間 :
交換 :
a 吃完的時間 :
b 吃完的時間 :
如果
則上面打勾的式子會是最大的,
代表不交換較好
因此我們將吃飯速度越慢的排越前面是好的
#include<bits/stdc++.h>
using namespace std;
#define maxn 10004
int n;
pair<int,int> p[maxn];
bool comp(pair<int,int> a,pair<int,int> b){
return a.second > b.second;
}
main(){
while(cin>>n && n){
for(int i=1;i<=n;++i) cin>>p[i].first>>p[i].second;
sort(p+1,p+1+n,comp);
int as = 0,t = 0;
for(int i=1;i<=n;++i){
t += p[i].first;
as = max(as,t+p[i].second);
}
cout<<as<<endl;
}
}先靠著直覺、經驗,想想看
有沒有可以greedy的地方
先靠著直覺、經驗,想想看
有沒有可以greedy的地方
接下來可以通過舉反例,或者上面示範的證明方法試著證明正確性
或者直接靠感覺