進階貪心--帶後悔的貪心(持續更新)
阿新 • • 發佈:2019-01-02
眾所周知,貪心的侷限性在於其只滿足當前的最優,無法保證全域性的最優,對於這種問題,常轉化為DP來解決,現在介紹一類變化的貪心,通過“後悔操作”,消除貪心的這一點缺陷。
由於刷題有限,只拿出了2道例題。
常用演算法/資料結構
輸入的第一行包含兩個正整數n、m。
第二行n個整數Ai。
Output
輸出一個整數,表示最佳植樹方案可以得到的美觀度。如果無解輸出“Error!”,不包含引號。
Sample Input
【樣例輸入1】
7 3
1 2 3 4 5 6 7
【樣例輸入2】
7 4
1 2 3 4 5 6 7
Sample Output
【樣例輸出1】 15
【樣例輸出2】 Error!
Hint
【資料規模】
對於全部資料: m<=n ; -1000<=Ai<=1000
N 的大小對於不同資料有所不同:
考慮這個問題的簡化版本:我們去掉“不能選相鄰的土地”的條件,那麼問題就變成了一個非常顯而易見的貪心:排序,選最大的m個。
回到這個問題上,還能不能這樣做呢?
假定有如下土地:
1 19 20 19 1 1
按照原先的貪心策略,果斷選擇20---等一下,如果選擇20,那麼旁邊的兩個19就不能選擇了!假設我們要種2棵樹,我們原來的貪心策略就是錯誤的。
(這裡,我們使用優先佇列來進行貪心。)
搞毛?貪心只能保證區域性正確!
現在介紹我們的後悔演算法:
選擇位置3的土地後,位置2和位置4的土地不能被選擇,這兩種選擇是衝突的--我們需要一種演算法,能反悔“選擇3”的操作,並且給出“選擇2和4”的選項。
--解決方案是簡單的,在選擇3後,刪除2和4,同時在1和5之間插入val[2]+val[4]-val[3]的值。
這是什麼效果呢?
假設選擇了3,再選擇我們新插入的值,就等效於選擇2和4,而不選擇3。意思是說,如果選擇2和4,而不選擇3的操作比單選擇3更優,我們就能夠正確地選到2和4.
當然,這個值一定是在選擇3之後才能加入的,不然會產生減多的錯誤。
經過這樣的操作,我們就能夠使得原來的傻貪心變成一個能夠後悔的貪心,從而得到正確解。
同時介紹一下pair的應用,如果有一個結構,只有2個元素,排序先看A元素,再看B元素,那麼就能使用如下程式碼:
Q.push(make_pair(a,b));
要使用a元素(假設這裡是一個堆)
Q.top().first;
使用B元素:
Q.top().second;
比過載運算子簡單得多呢!
下面給出這一道題的程式碼。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
//#include<cmath>
#include<queue>
#define pa pair<int,int>
using namespace std;
priority_queue<pa> Q;
int next[200005]={0},pre[200005]={0},fl[200005]={0},n,m,ans=0,a[200005]={0};
inline int read()
{
int bj=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')bj=-1;
ch=getchar();
}
int ret=0;
while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
return ret*bj;
}
void init()
{
n=read();m=read();
if(m*2>n){puts("Error!");exit(0);}
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++)
{
next[i]=i+1;
pre[i]=i-1;
Q.push(make_pair(a[i],i));
}
pre[1]=n;
next[n]=1;
}
void del(int x)
{
next[pre[x]]=next[x];
pre[next[x]]=pre[x];
fl[x]=1;
}
void greed()
{
while(fl[Q.top().second])Q.pop();
int x=Q.top().second;Q.pop();
ans+=a[x];
a[x]=a[pre[x]]+a[next[x]]-a[x];
del(pre[x]);
del(next[x]);
Q.push(make_pair(a[x],x));
}
void work()
{
for(int i=1;i<=m;i++)greed();
printf("%d\n",ans);
}
int main()
{
init();
work();
return 0;
}
4211 -- 【USACO 2013 FEB】買牛
Description
FJ看中了N(1 <= N <= 50,000)頭牛,他有m (1 <= M <= 10^14)元,還有k(1 <= K <= N)張優惠券。第i頭牛的價格為P_i (1 <= P_i <= 10^9),如果使用優惠券則為C_i (1 <= C_i <= P_i)。問農夫最多能買到多少頭牛。
Input
* 行1: 三個整數: N, K, and M.
* 行2..N+1: 每行兩個整數 P_i 和 C_i.
Output 一個整數為買到牛數量 Sample Input 4 1 7 3 2 2 2 8 1 4 3 Sample Output 3 Hint 樣例說明: FJ uses the coupon on cow 3 and buys cows 1, 2, and 3, for a total cost of 3 + 2 + 1 = 6. 一樣的思想,加一個後悔值到堆裡面去進行貪心調整即可 ^_^ 把題解複製了過來: 顯然要儘量使用優惠券。首先我們按C_i排序,按順序買直到k頭。有一個小優化:如果此時沒買到k頭,直接輸出買到的數目即可。 然後我們把剩下的按P_i排序,直接按順序買行嗎?顯然是不行的,因為可能這些牛使用優惠券省的錢更多。如何處理,我們可以將C_i、P_i分別排序,採用撤銷策略,維護一個用了優惠券的牛的P-C的值的大根堆,你最少可以用堆頂的值t來“買”(即撤銷原用的優惠券)一張優惠券。那麼我們就用基本最小的P與t加最小的C中較小的值來買這頭牛,如果採用t+C就替換原來堆頂。 //值得一提的是,這道題簡直水的一B,直接一個堆+錯誤貪心就能80分.. #include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
struct node
{
long long p,c;
}a[50005]={0};
priority_queue<long long,vector<long long>,greater<long long> > Q;
long long n,k,m,ans=0,j;
bool cmp(node x,node y)
{
return x.c<y.c;
}
int main()
{
cin>>n>>k>>m;
for(long long i=1;i<=n;i++)cin>>a[i].p>>a[i].c;
sort(a+1,a+n+1,cmp);
for(long long i=1;i<=k;i++)
{
if(a[i].c>m)continue;
m-=a[i].c;
ans++;
j=i;
Q.push(a[i].p-a[i].c);
}
if(j<k){cout<<ans;return 0;}
j++;
while(j<=n)
{
long long del=Q.top();
if(min(a[j].c+del,a[j].p)<=m)
{
ans++;
m-=min(a[j].c+del,a[j].p);
if(a[j].c+del<a[j].p)
{
Q.pop();Q.push(a[j].p-a[j].c);
}
}
j++;
}
cout<<ans;
return 0;
}
這裡只是介紹了一種優化貪心本質的方法,應用比較廣,2道題不能概括全部,但重要的是理解到這種方法。 NOIP裡面貌似不是很喜歡考這類題目?權當思維體操了。
- 優先佇列
- 貪心思想
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
//#include<cmath>
#include<queue>
#define pa pair<int,int>
using namespace std;
priority_queue<pa> Q;
int next[200005]={0},pre[200005]={0},fl[200005]={0},n,m,ans=0,a[200005]={0};
inline int read()
{
int bj=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')bj=-1;
ch=getchar();
}
int ret=0;
while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
return ret*bj;
}
void init()
{
n=read();m=read();
if(m*2>n){puts("Error!");exit(0);}
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++)
{
next[i]=i+1;
pre[i]=i-1;
Q.push(make_pair(a[i],i));
}
pre[1]=n;
next[n]=1;
}
void del(int x)
{
next[pre[x]]=next[x];
pre[next[x]]=pre[x];
fl[x]=1;
}
void greed()
{
while(fl[Q.top().second])Q.pop();
int x=Q.top().second;Q.pop();
ans+=a[x];
a[x]=a[pre[x]]+a[next[x]]-a[x];
del(pre[x]);
del(next[x]);
Q.push(make_pair(a[x],x));
}
void work()
{
for(int i=1;i<=m;i++)greed();
printf("%d\n",ans);
}
int main()
{
init();
work();
return 0;
}
4211 -- 【USACO 2013 FEB】買牛
Output 一個整數為買到牛數量 Sample Input 4 1 7 3 2 2 2 8 1 4 3 Sample Output 3 Hint 樣例說明: FJ uses the coupon on cow 3 and buys cows 1, 2, and 3, for a total cost of 3 + 2 + 1 = 6. 一樣的思想,加一個後悔值到堆裡面去進行貪心調整即可 ^_^ 把題解複製了過來: 顯然要儘量使用優惠券。首先我們按C_i排序,按順序買直到k頭。有一個小優化:如果此時沒買到k頭,直接輸出買到的數目即可。 然後我們把剩下的按P_i排序,直接按順序買行嗎?顯然是不行的,因為可能這些牛使用優惠券省的錢更多。如何處理,我們可以將C_i、P_i分別排序,採用撤銷策略,維護一個用了優惠券的牛的P-C的值的大根堆,你最少可以用堆頂的值t來“買”(即撤銷原用的優惠券)一張優惠券。那麼我們就用基本最小的P與t加最小的C中較小的值來買這頭牛,如果採用t+C就替換原來堆頂。 //值得一提的是,這道題簡直水的一B,直接一個堆+錯誤貪心就能80分.. #include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
struct node
{
long long p,c;
}a[50005]={0};
priority_queue<long long,vector<long long>,greater<long long> > Q;
long long n,k,m,ans=0,j;
bool cmp(node x,node y)
{
return x.c<y.c;
}
int main()
{
cin>>n>>k>>m;
for(long long i=1;i<=n;i++)cin>>a[i].p>>a[i].c;
sort(a+1,a+n+1,cmp);
for(long long i=1;i<=k;i++)
{
if(a[i].c>m)continue;
m-=a[i].c;
ans++;
j=i;
Q.push(a[i].p-a[i].c);
}
if(j<k){cout<<ans;return 0;}
j++;
while(j<=n)
{
long long del=Q.top();
if(min(a[j].c+del,a[j].p)<=m)
{
ans++;
m-=min(a[j].c+del,a[j].p);
if(a[j].c+del<a[j].p)
{
Q.pop();Q.push(a[j].p-a[j].c);
}
}
j++;
}
cout<<ans;
return 0;
}
這裡只是介紹了一種優化貪心本質的方法,應用比較廣,2道題不能概括全部,但重要的是理解到這種方法。 NOIP裡面貌似不是很喜歡考這類題目?權當思維體操了。