1. 程式人生 > >進階貪心--帶後悔的貪心(持續更新)

進階貪心--帶後悔的貪心(持續更新)

眾所周知,貪心的侷限性在於其只滿足當前的最優,無法保證全域性的最優,對於這種問題,常轉化為DP來解決,現在介紹一類變化的貪心,通過“後悔操作”,消除貪心的這一點缺陷。 由於刷題有限,只拿出了2道例題。 常用演算法/資料結構
  • 優先佇列
  • 貪心思想
3055 -- 【八校聯考2】種樹 Description   A城市有一個巨大的圓形廣場,為了綠化環境和淨化空氣,市政府決定沿圓形廣場外圈種一圈樹。園林部門得到指令後,初步規劃出n個種樹的位置,順時針編號1到n。並且每個位置都有一個美觀度Ai,如果在這裡種樹就可以得到這Ai的美觀度。但由於A城市土壤肥力欠佳,兩棵樹決不能種在相鄰的位置(i號位置和i+1號位置叫相鄰位置。值得注意的是1號和n號也算相鄰位置!)。   最終市政府給園林部門提供了m棵樹苗並要求全部種上,請你幫忙設計種樹方案使得美觀度總和最大。如果無法將m棵樹苗全部種上,給出無解資訊。 Input
輸入的第一行包含兩個正整數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裡面貌似不是很喜歡考這類題目?權當思維體操了。