AcWing 393 僱傭收銀員

昂昂累世士發表於2020-10-25

題目描述:

一家超市要每天24小時營業,為了滿足營業需求,需要僱傭一大批收銀員。

已知不同時間段需要的收銀員數量不同,為了能夠僱傭儘可能少的人員,從而減少成本,這家超市的經理請你來幫忙出謀劃策。

經理為你提供了一個各個時間段收銀員最小需求數量的清單R(0),R(1),R(2),…,R(23)。

R(0)表示午夜00:00到凌晨01:00的最小需求數量,R(1)表示凌晨01:00到凌晨02:00的最小需求數量,以此類推。

一共有N個合格的申請人申請崗位,第 i 個申請人可以從titi時刻開始連續工作8小時。

收銀員之間不存在替換,一定會完整地工作8小時,收銀臺的數量一定足夠。

現在給定你收銀員的需求清單,請你計算最少需要僱傭多少名收銀員。

輸入格式

第一行包含一個不超過20的整數,表示測試資料的組數。

對於每組測試資料,第一行包含24個整數,分別表示R(0),R(1),R(2),…,R(23)。

第二行包含整數N。

接下來N行,每行包含一個整數titi。

輸出格式

每組資料輸出一個結果,每個結果佔一行。

如果沒有滿足需求的安排,輸出“No Solution”。

資料範圍

0≤R(0)≤1000,
0≤N≤1000,
0≤ti≤23

輸入樣例:

1
1 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
5
0
23
22
1
10

輸出樣例:

1

分析:

本題相對與前幾題的難點在於約束條件的轉換以及對出現了三個變數不等式的處理。首先,根據題意總結出約束條件。

題目輸入有每個員工到達的時間ti,不管有多少個員工,ti永遠是0到23之間的整數,我們只關注每個整點能到達的員工數,所以用num[i]記錄下在i時刻到達的員工數,我們要做的就是在每個整點選出xi個員工,在符合題目要求的同時使得xi的和最小。首先,i時刻選出的收銀員一定不會超過num[i],即0 <= xi <= num[i] 。又R[i]表示i到i+1時段至少需要的員工數,由於每個員工都會連續工作8小時,所以i到i+1時刻實際工作的員工數為xi-7 + ... xi >= R[i]。由於八個小時內員工的總數需要經常參與計算,所以使用字首和來快速的表示出這段時間的員工數。原本R[i]是表示i到i+1時段至少需要的員工數,R[0]也是有實際含義的,但是字首和需要空出s[0],所以改為用區間的右端點表示這段區間。即R[i]表示i - 1到i時段至少需要的員工數。xi = si - si - 1,推出了0 <= si - si-1 <= num[i],(1 <= i <= 24)。同樣,xi-7+ ... +xi = si - si-8 >= R[i],(8 <= i <= 24),那麼i <= 7的情況呢,si往前數8個就是si,si-1,...,s1,s24,...,si+17,可以計算出si+17到s24一共24 - i- 17 + 1 = 8- i個元素,再加上s1到si的i個元素就是8個元素,處在前面的i個元素的和可以用si表示,處在後面的8 - i個元素的和可以用s24 - si+16表示,所以推出了si + s24 - si+16 >= R[i],(1 <= i <= 7)。總結一下這裡推出的約束條件就是:

0 <= si - si-1 <= num[i],(1 <= i <= 24)

si - si-8 >= R[i],(8 <= i <= 24)

si + s24 - si+16 >= R[i],(1 <= i <= 7)

我們需要從超級源點s0出發,求出到s24的最長路,s24就是問題的解了。注意到上面的第三個約束條件不符合差分約束的標準形式,一般的差分約束的不等式是a >= b + c,其中c是常數,這裡卻出現了si,s24,si+16三個變數。注意到題目的n不超過1000,我們可以列舉問題的解,從0到n列舉s24的值,直到出現合法解為止。因為我們最壞情況下就是把n個來應聘的都招進來,所以s24的最大值只能是n。這裡n的範圍不大,圖中的點數和邊數也少,直接列舉即可,當然二分答案也是可以的,如果s24 = 10合法,那麼多招一個人肯定也是合法的。注意這裡對s24的列舉不是白白列舉的,列舉s24就意味著加上了s24 = i的條件,所以需要加上s24 <= s0 + i和s24 >= s0 + i這兩個約束條件。

有沒有辦法不通過列舉答案來解決本題呢?我最初的思路是第三個約束條件之所以出現,是因為si-8的下標不能是負數,也是因為時間是迴圈的,相當於對一個環形陣列求字首和。根據求解環形陣列字首和的思想,如果將一天時間寫兩遍,1,2,3,...,24,1,2,3,...,24這樣一個長度為48的陣列裡就可以出現任意一個連續的8小時時間了,比如23,24,1,2,3,4,5,6,即使結束時間6是小於8的。這意味著可以將約束條件改為si - si-8 >= R[i],(8 <= i <= 32),因為實際只需要多寫8個時刻就可以表示出所有連續的8小時,所以約束條件範圍寫到32即可。但是這種解法還是有缺陷的,因為我們在引入s25到s32這麼多新變數的同時,還需要加上新的約束條件,比如s26 = s2 + s24,這種條件保證了x2和x26是相等的,即這天2點鐘和第二天2點鐘員工數是相等的,而新加的約束條件不可避免的也引入了第三個變數s24,所以這種思想最終還是要列舉s24的值,並不能簡化問題。總的程式碼如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 30,M = 100;
int idx,h[N],e[M],w[M],ne[M];
int q[N],cnt[N],d[N],r[N],num[N];
bool st[N];
void add(int a,int b,int c){
    e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++;
}
void build(int s){
    idx = 0;
    memset(h,-1,sizeof h);
    add(0,24,s),add(24,0,-s);
    for(int i = 1;i <= 24;i++){
        add(i - 1,i,0);
        add(i,i - 1,-num[i]);
    }
    for(int i = 8;i <= 24;i++){
        add(i - 8,i,r[i]);
    }
    for(int i = 1;i < 8;i++){
        add(i + 16,i,r[i] - s);
    }
}
bool spfa(){
    memset(d,-0x3f,sizeof d);
    memset(cnt,0,sizeof cnt);
    memset(st,false,sizeof st);
    int hh = 0,tt = 1;
    q[0] = 0;
    st[0] = true;
    d[0] = 0;
    while(hh != tt){
        int u = q[hh++];
        if(hh == N) hh = 0;
        st[u] = false;
        for(int i = h[u];~i;i = ne[i]){
            int j = e[i];
            if(d[j] < d[u] + w[i]){
                d[j] = d[u] + w[i];
                cnt[j] = cnt[u] + 1;
                if(cnt[j] >= 25)  return false;
                if(!st[j]){
                    q[tt++] = j;
                    if(tt == N) tt = 0;
                    st[j] = true;
                }
            }
        }
    }
    return true;
}
int main(){
    int T,n,t;
    scanf("%d",&T);
    while(T--){
        memset(num,0,sizeof num);
        for(int i = 1;i <= 24;i++)  scanf("%d",&r[i]);
        scanf("%d",&n);
        for(int i = 1;i <= n;i++){
            scanf("%d",&t);
            num[t + 1]++;
        }
        bool flag = true;
        for(int i = 0;i <= n;i++){
            build(i);
            if(spfa()){
                printf("%d\n",i);
                flag = false;
                break;
            }
        }
        if(flag)    puts("No Solution");
    }
    return 0;
}

 

相關文章