1. 程式人生 > >[洛谷 P1251]餐巾計劃問題---三分+ 貪心

[洛谷 P1251]餐巾計劃問題---三分+ 貪心

傳送門:洛谷 P1251


題目描述

一個餐廳在相繼的 N N 天裡,每天需用的餐巾數不盡相同。假設第 i i 天需要 r

i r_i 塊餐巾 ( i = 1 , 2 ,
, N ) (i=1,2, \dots ,N)
。餐廳可以購買新的餐巾,每塊餐巾的費用為 p p
分;或者把舊餐巾送到快洗部,洗一塊需 m m 天,其費用為 f f 分;或者送到慢洗部,洗一塊需 n n 天( n > m n>m ),其費用為 s s 分( s < f s<f )。

每天結束時,餐廳必須決定將多少塊髒的餐巾送到快洗部,多少塊餐巾送到慢洗部,以及多少塊儲存起來延期送洗。但是每天洗好的餐巾和購買的新餐巾數之和,要滿足當天的需求量。

試設計一個演算法為餐廳合理地安排好 N N 天中餐巾使用計劃,使總的花費最小。程式設計找出一個最佳餐巾使用計劃。


分析

費用流的解法是有的,然而效率太低(不會 /逃)。為何不考慮貪心呢。
貪心
假設我們已經確定了一共要買的餐巾數,那麼,就可考慮用貪心求出最小的費用了。
對於每天需要的餐巾,一共有一下三種來源:

  1. 未用過的餐巾
    已經買了,免費
  2. 前面慢洗出來的餐巾
    越早越好(留給後面的慢洗)
  3. 前面快洗出來的餐巾
    越晚越好(儘可能留給慢洗)

根據題目的條件,可以保證其費用是遞增的。
至於實現,只需要記錄每一天能夠用來換洗的餐巾數即可。
三分
至於如何確定要買的餐巾數,列舉是一定 O K OK 的,只不過效率太低。
我們可以猜測一下餐巾數與最小費用之間的關係,應該近似單谷函式
感性的認識:若餐巾數過小,則會無解,可視為無窮大;若餐巾數過多,則無需換洗餐巾,但換洗餐巾必定比不換洗要優。
於是,就愉快的三分吧(三分和二分差不多,隨便看看就行了)。


程式碼

#include <cstdio>
#include <cstdlib>
#include <cstring>

#define IL inline
#define ll long long

using namespace std;

const int maxn = 2000 + 5;
const ll INF = 0xfffffffffffff; //ans比較大,注意無窮大的取值

IL int read()
{
    char c = getchar();
    int sum = 0 ,k = 1;
    for(;'0' > c || c > '9'; c = getchar())
        if(c == '-') k = -1;
    for(;'0' <= c && c <= '9'; c = getchar()) sum = sum * 10 + c - '0';
    return sum * k;
}

int n;
int t1, t2;
ll p0, p1, p2;
int need[maxn], num[maxn];
//需要的餐巾數  ,  每天可換洗的餐巾數
IL ll min_(ll x, ll y) { return x < y ? x : y; }

IL ll check(ll rest)
{
    memset(num, 0, sizeof(num));
    int tp = 1;
    ll price = rest * p0; //先買下來
    for(int t = 1, k, ned; t <= n; ++t)
    if(need[t])
    {
        ned = need[t];
        if(rest)//如果有剩餘
        {
            k = min_(ned, rest);
            rest -= k;
            num[t] += k;
            ned -= k;
            if(!ned) continue;
        }
        
        for(; tp < t && !num[tp]; ++tp);//算是一個優化,找到最早的能換洗餐巾的時間
        for(int i = tp; i <= t - t2; ++i)//慢洗,從前往後
        if(num[i])
        {
            k = min_(ned, num[i]);
            num[i] -= k;
            num[t] += k;
            price += k * p2;
            ned -= k;
            if(!ned) continue ;
        }
        for(int i = t - t1; i >= 1 && i > t - t2; --i)//快洗, 從後往前
        if(num[i])
        {
            k = min_(ned, num[i]);
            num[i] -= k;
            num[t] += k;
            price += k * p1;
            ned -= k;
            if(!ned) continue;
        }
        if(ned) return INF; //如果能用的都用上了,但還是滿足不了條件,那就無解了
    }
    return price;
}

int main()
{
    ll l = 0, r = 0;
    n = read();
    for(int i = 1; i <= n; ++i) { need[i] = read(); r += need[i]; }//頭痛,解釋不了
    p0 = read(); t1 = read(); p1 = read(); t2 = read(); p2 = read();
    l = need[1];
    //標準式三分
    for(ll k, lmid, rmid, s1, s2;l + 2 < r;)
    {
    	k = (r - l) / 3;
    	lmid = l + k;
    	rmid = r - k;
    	if(check(lmid) >= check(rmid)) l = lmid; else r = rmid;
    }
    ll ans = check(l);
    for(++l;l <= r; ++l)
        ans = min_(ans, check(l));
    printf("%lld", ans);
    return 0;
}