更好的视觉体验 : https://xiaoxiaoh.blog.csdn.net/article/details/104193486
一、内容
A supermarket in Tehran is open 24 hours a day every day and needs a number of cashiers to fit its need. The supermarket manager has hired you to help him, solve his problem. The problem is that the supermarket needs different number of cashiers at different times of each day (for example, a few cashiers after midnight, and many in the afternoon) to provide good service to its customers, and he wants to hire the least number of cashiers for this job.
The manager has provided you with the least number of cashiers needed for every one-hour slot of the day. This data is given as R(0), R(1), ..., R(23): R(0) represents the least number of cashiers needed from midnight to 1:00 A.M., R(1) shows this number for duration of 1:00 A.M. to 2:00 A.M., and so on. Note that these numbers are the same every day. There are N qualified applicants for this job. Each applicant i works non-stop once each 24 hours in a shift of exactly 8 hours starting from a specified hour, say ti (0 <= ti <= 23), exactly from the start of the hour mentioned. That is, if the ith applicant is hired, he/she will work starting from ti o'clock sharp for 8 hours. Cashiers do not replace one another and work exactly as scheduled, and there are enough cash registers and counters for those who are hired.
You are to write a program to read the R(i) 's for i=0..23 and ti 's for i=1..N that are all, non-negative integer numbers and compute the least number of cashiers needed to be employed to meet the mentioned constraints. Note that there can be more cashiers than the least number needed for a specific slot.
Input
The first line of input is the number of test cases for this problem (at most 20). Each test case starts with 24 integer numbers representing the R(0), R(1), ..., R(23) in one line (R(i) can be at most 1000). Then there is N, number of applicants in another line (0 <= N <= 1000), after which come N lines each containing one ti (0 <= ti <= 23). There are no blank lines between test cases.
Output
For each test case, the output should be written in one line, which is the least number of cashiers needed.
If there is no solution for the test case, you should write No Solution for that case.
Sample Input
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
Sample Output
1
二、思路
- 差分约束
- num[i]: 代表i时刻的收银员人数, p[i]代表i时候至少需要的收银员人数。 s[i]:代表0~i 这个时间段收银员的人数。(前缀和数组) 由于要用到前缀和数组,所以我们让0 ~ 23 时刻转化为 1 ~ 24时刻。 这样才能使 s[0] = 0。
- 推导不等式:
- 判断条件: 通过0点就能遍历所有的边,符合差分约束的条件。
- 求最小值-----求最长路---- 将不等式转化为 S~a~ >= S~b~ + c 的形式
- 由于我们是通过枚举的s[24]的值,那么我们必须将它变成一个定值。那么我们可以建立2条边即可。s[24] >= s[0] + k, s[0] >= s[24] - k。 这样我们就通过超级源点0点将s[24]的值变成定值。 (s[0] = 0)
- 二分优化: s[24]的值我们是通过枚举的。 实际上答案肯定是满足单调性的。当最少的人数满足时,比这个最少人数多的都算满足。那么我们就可以二分这个区间。【0,n+1】 n+1代表的是不合法的区间,因为当n个人都无法满足 ,那么答案就会跳到n+1,我们直接判断n+1即可。
三、代码
无优化:
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int N = 25, M = 100;
struct E {
int v, w, next;
} e[M];
int t, n, len, k, p[N], d[N], h[N], num[N], cnt[N];
bool vis[N];
void add(int u, int v, int w) {
e[len].v = v;
e[len].w = w;
e[len].next = h[u];
h[u] = len++;
}
bool spfa(int s24) {
memset(h, 0, sizeof(h)), len = 1;
memset(d, -0x3f, sizeof(d));//最小值求最长路
memset(cnt, 0, sizeof(cnt));
memset(vis, false, sizeof(vis));
d[0] = 0; //从0可以遍历所有的边
//根据枚举的s24进行建图
for (int i = 1; i <= 24; i++) {
add(i - 1, i, 0), add(i, i - 1, -num[i]);
if (i >= 8) {
add(i - 8, i, p[i]);
} else {
add(i + 16, i, -s24 + p[i]);
}
}
//由于我们还需要设置 s24为定值 所以创建 s24<=k s24 >= k 这样s24就是定值了(k是枚举的s24的值)
add(0, 24, s24), add(24, 0, -s24);
//spfa看是否有解
queue<int> q;
q.push(0);
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = false;
for (int j = h[u]; j; j = e[j].next) {
int v = e[j].v;
int w = d[u] + e[j].w;
if (w > d[v]) {
d[v] = w;
cnt[v] = cnt[u] + 1;
if (cnt[v] >= 25) return true;
if (!vis[v]) q.push(v), vis[v] = true;
}
}
}
return false;
}
int main() {
scanf("%d", &t);
while (t--) {
memset(num, 0, sizeof(num));//num[i] 代表i时刻的人数
for (int i = 1; i <= 24; i++) scanf("%d", &p[i]);
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &k);
num[++k]++;
}
//枚举一下s24的值
bool ok = false;
for (int i = 1; i <= n; i++) {
if (!spfa(i)) {
//找到一组结果
printf("%d\n", d[24]); //输出一下一共需要的最少人数
ok = true;
break;
}
}
if (!ok) printf("No Solution\n");
}
return 0;
}
二分优化:
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int N = 25, M = 100;
struct E {
int v, w, next;
} e[M];
int t, n, len, k, p[N], d[N], h[N], num[N], cnt[N];
bool vis[N];
void add(int u, int v, int w) {
e[len].v = v;
e[len].w = w;
e[len].next = h[u];
h[u] = len++;
}
bool spfa(int s24) {
memset(h, 0, sizeof(h)), len = 1;
memset(d, -0x3f, sizeof(d));//最小值求最长路
memset(cnt, 0, sizeof(cnt));
memset(vis, false, sizeof(vis));
d[0] = 0; //从0可以遍历所有的边
//根据枚举的s24进行建图
for (int i = 1; i <= 24; i++) {
add(i - 1, i, 0), add(i, i - 1, -num[i]);
if (i >= 8) {
add(i - 8, i, p[i]);
} else {
add(i + 16, i, -s24 + p[i]);
}
}
//由于我们还需要设置 s24为定值 所以创建 s24<=k s24 >= k 这样s24就是定值了(k是枚举的s24的值)
add(0, 24, s24), add(24, 0, -s24);
//spfa看是否有解
queue<int> q;
q.push(0);
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = false;
for (int j = h[u]; j; j = e[j].next) {
int v = e[j].v;
int w = d[u] + e[j].w;
if (w > d[v]) {
d[v] = w;
cnt[v] = cnt[u] + 1;
if (cnt[v] >= 25) return true;
if (!vis[v]) q.push(v), vis[v] = true;
}
}
}
return false;
}
int main() {
scanf("%d", &t);
while (t--) {
memset(num, 0, sizeof(num));//num[i] 代表i时刻的人数
for (int i = 1; i <= 24; i++) scanf("%d", &p[i]);
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &k);
num[++k]++;
}
//枚举一下s24的值 可以进行二分枚举
int l = 0, r = n;
while (l < r) {
int mid = (l + r) >> 1;
if (spfa(mid)) {
l = mid + 1;
} else {
r = mid;
}
}
if (l == 0) printf("No Solution\n");
else printf("%d\n", l);
}
return 0;
}
这里二分写错了,r应该等于n+1 ,或者l = -1不然无法判断到底二分出来的结果是无解还是有解.
二分那里有点问题, 答案区间为 0~n, 你直接让 l = 0, r = n, 那你判断 no solution 时就有问题, 要么让 l = -1, 要么让 r = n + 1, 不卡住答案区间的裤腰带, 二分无解是没法判断的