题目地址:
https://www.acwing.com/problem/content/395/
一家超市要每天
24
24
24小时营业,为了满足营业需求,需要雇佣一大批收银员。已知不同时间段需要的收银员数量不同,为了能够雇佣尽可能少的人员,从而减少成本,这家超市的经理请你来帮忙出谋划策。经理为你提供了一个各个时间段收银员最小需求数量的清单
R
(
0
)
,
R
(
1
)
,
R
(
2
)
,
…
,
R
(
23
)
R(0),R(1),R(2),…,R(23)
R(0),R(1),R(2),…,R(23)。
R
(
0
)
R(0)
R(0)表示午夜00:00
到凌晨01:00
的最小需求数量,
R
(
1
)
R(1)
R(1)表示凌晨01:00
到凌晨02:00
的最小需求数量,以此类推。一共有
N
N
N个合格的申请人申请岗位,第
i
i
i个申请人可以从
t
i
t_i
ti时刻开始连续工作
8
8
8小时。收银员之间不存在替换,一定会完整地工作
8
8
8小时,收银台的数量一定足够。现在给定你收银员的需求清单,请你计算最少需要雇佣多少名收银员。
输入格式:
第一行包含一个不超过
20
20
20的整数,表示测试数据的组数。对于每组测试数据,第一行包含
24
24
24个整数,分别表示
R
(
0
)
,
R
(
1
)
,
R
(
2
)
,
…
,
R
(
23
)
R(0),R(1),R(2),…,R(23)
R(0),R(1),R(2),…,R(23)。第二行包含整数
N
N
N。接下来
N
N
N行,每行包含一个整数
t
i
t_i
ti。
输出格式:
每组数据输出一个结果,每个结果占一行。如果没有满足需求的安排,输出No Solution
。
数据范围:
0
≤
R
(
0
)
≤
1000
0≤R(0)≤1000
0≤R(0)≤1000
0
≤
N
≤
1000
0≤N≤1000
0≤N≤1000
0
≤
t
i
≤
23
0≤t_i≤23
0≤ti≤23
先设出每小时有多少个收银员在工作,这样原题就转化为了一系列不等式。设 n i n_i ni表示第 i i i个小时( i i i从 1 1 1开始计数,下面也一样)有多少个人来应聘了。在读入了所有的 t i t_i ti的值的时候, n t i + 1 n_{t_i+1} nti+1应当递增 1 1 1,表示 t i + 1 t_i+1 ti+1这个小时的备选人数多了 1 1 1。设 x i x_i xi是第 i i i个小时开始工作的所有应聘的人里,挑了多少个,那么 0 ≤ x i ≤ n i 0\le x_i\le n_i 0≤xi≤ni。此外,要求第 i i i小时总共能提供服务的人数要大于等于 R ( i − 1 ) R(i-1) R(i−1),所以有 x i − 7 + x i − 6 + . . . + x i ≥ R ( i − 1 ) x_{i-7}+x_{i-6}+...+x_i\ge R(i-1) xi−7+xi−6+...+xi≥R(i−1)(这里可以在读入的时候,将 R ( 0 ) R(0) R(0)读成 r 1 r_1 r1,以此类推,以保证下标的一致性)。为了能用差分约束来做,可以用前缀和思想,令 s i = x 0 + . . . + x i − 1 s_i=x_0+...+x_{i-1} si=x0+...+xi−1, s 0 = 0 s_0=0 s0=0。这样就有: { 0 ≤ s i − s i − 1 ≤ n i s i − s i − 8 ≥ r i , i ≥ 8 s i + ( s 24 − s i + 16 ) ≥ r i , 0 < i < 7 \begin{cases}0\le s_i-s_{i-1}\le n_i\\s_i-s_{i-8}\ge r_i, i\ge 8\\ s_i+(s_{24}-s_{i+16})\ge r_i, 0<i < 7\end{cases} ⎩⎪⎨⎪⎧0≤si−si−1≤nisi−si−8≥ri,i≥8si+(s24−si+16)≥ri,0<i<7我们可以枚举 s 24 s_{24} s24,直到上述差分约束有解为止。为了实现这一点,我们要将上面的最后一个不等式拆成三个: s i + ( s 24 − s i + 16 ) ≥ r i , 0 < i < 7 ⇔ { s i − s i + 16 ≥ r i − c , 0 < i < 7 s 24 ≤ s 0 + c s 24 ≥ s 0 + c s_i+(s_{24}-s_{i+16})\ge r_i, 0<i < 7\Leftrightarrow\\\begin{cases} s_i-s_{i+16}\ge r_i-c, 0<i<7\\ s_{24}\le s_0+c \\s_{24}\ge s_0+c \end{cases} si+(s24−si+16)≥ri,0<i<7⇔⎩⎪⎨⎪⎧si−si+16≥ri−c,0<i<7s24≤s0+cs24≥s0+c这样枚举 c c c就行了。这个问题只需判断某个差分约束是否有解,所以求最短路最长路都是可以的。求最长(短)路的时候,可以从 0 0 0号点开始搜,因为 0 0 0可以到达别的任意点(当然也可以同时从所有点开始搜,即等价于开个超级源点,该点到其余每个点距离都是 0 0 0,然后从超级源点开始搜)。发现存在正环则说明无解,即 c c c不合法,要重新尝试另一个 c c c,否则说明有解。此外,枚举 c c c的过程可以用二分来做,因为如果对于某个人数 a a a来说有解,那么对于大于 a a a的人数一定也是有解的。代码如下:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 30, M = 100;
int n;
int h[N], e[M], ne[M], w[M], idx;
int r[N], num[N];
int dist[N];
int q[N], cnt[N];
bool st[N];
void add(int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
void build(int c) {
memset(h, -1, sizeof h);
idx = 0;
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 <= 7; i++) add(i + 16, i, -c + r[i]);
add(0, 24, c);
add(24, 0, -c);
}
// spfa找正环
bool spfa(int s24) {
build(s24);
memset(st, 0, sizeof st);
memset(cnt, 0, sizeof cnt);
memset(dist, 0, sizeof dist);
int hh = 0, tt = 0;
for (int i = 0; i <= 24; i++) {
q[tt++] = i;
st[i] = true;
}
while (hh != tt) {
int t = q[hh++];
if (hh == N) hh = 0;
st[t] = false;
for (int i = h[t]; ~i; i = ne[i]) {
int v = e[i];
if (dist[v] < dist[t] + w[i]) {
dist[v] = dist[t] + w[i];
cnt[v] = cnt[t] + 1;
// 一共25个点,发现正环了则返回false
if (cnt[v] >= 25) return false;
if (!st[v]) {
q[tt++] = v;
if (tt == N) tt = 0;
st[v] = true;
}
}
}
}
return true;
}
int main() {
int T;
cin >> T;
while (T--) {
for (int i = 1; i <= 24; i++) cin >> r[i];
cin >> n;
memset(num, 0, sizeof num);
for (int i = 0; i < n; i++) {
int t;
cin >> t;
num[t + 1]++;
}
// 二分总人数
int lo = 0, ri = n;
while (lo < ri) {
int mid = lo + (ri - lo >> 1);
if (spfa(mid)) ri = mid;
else lo = mid + 1;
}
if (!spfa(lo)) puts("No Solution");
else cout << lo << endl;
}
return 0;
}
时间复杂度 O ( m n log r ) O(mn\log r) O(mnlogr), n n n和 m m m分别是图的点数和边数, r r r是搜索区间,空间 O ( n ) O(n) O(n)。