差分约束+二分答案
这道题和一般的差分约束有点不一样。 假设我们定s[i]为从起点到当前节点i一共需要雇佣的员工数,如果以s[i]作为状态解决问题的话,我们可以发现很难在这个问题上面找出起点是谁。我们假设s[0]为起点,那么s[23]就是从起点s[0]到s[23]之间的时间段需要雇佣的员工数,显然通过s[0]不断向前迭代,最终会影响s[23]的取值。然而,23的下一个时间段却是0,整个时间段是循环进行的,那么s[0]的结果又会因为s[23]的向前迭而受到影响,如果硬是要构图的话,会发现整个图是一个圈。一般的差分约束总是能确定起点的。
我们这里假设以s[0]作为起点,把不等式列出来:
s[i]-s[i-8] >= r[i] (r[i]为i这个时间段至少需要的员工数,因为雇佣一个员工能连续工作8小时,所以有i-8的取值,这里i的范围是 i >= 8)
s[i]+s[23]-s[23-(8-i)+1] >= r[i] (如果i < 8,那么除了取前i个时间段雇佣的员工数之外,还需要取前天的员工数,取够8小时)
s[i]-s[i-1] <= cnt[i] (cnt[i] 表示的是在i这个时间段总共可以雇佣的员工数)
s[i]-s[i-1] >= 0 (两个时间段的雇佣人数不能小于0)
除了第二个不等式以外,其他不等式都是正常的。
对于第二个不等式,这里的做法是枚举s[23]的取值结果,得出最小的答案。
网上有不上人的博客说这里的s[23]是一个常量,所以可以二分答案去枚举。
但这里的s[23]严格来说应该是一个动态的值,因为差分约束的可行解本就不是唯一的。
我们把第二个不等式化简下得到:s[i]-s[16+i] >= r[i]-s[23]
我们在枚举s[23]这个值的时候一定要弄清楚一个问题。s[23]取小和取大的情况下有什么区别。
取小:
通过简化的不等式,我们能知道s[i]-s[16+i]有一个最低下限的取值,也就是说s[i]到s[16+i]之间的员工数至少要有 x = r[i]-s[23]。这里要知道的是,如果x取得小一点,就相当于约束就会变松,可行解也就更广,对于本题的问题来说,最低下限肯定越小越好,最好是每一个时间段都不需要员工,这样就不用花钱雇了。约束条件松了,那么得到的可行解未必是原问题的可行解,这个是取小的情况下要明白的问题。
取大:
取大则和取小是相反的,约束的下限x变严格了,得到的可行解肯定是包含在原问题的可行解当中的。
所以取大才是我们真正要的情况,取小虽然可能是可行解,但也可能不是。
x 取大也就意味着枚举的s[23]要取小
上面的两种情况要好好捋一捋。
通过上面的结论,我们就能总结出最后一个约束条件的不等式 (最小解)s[23] >= (枚举的解)s[23]
总结下来的约束条件则是:(ans 是二分答案枚举的值)
s[i]-s[i-8] >= r[i] (8 <= i <= 23)
s[i]-s[16+i] >= r[i]-ans (0 <= i < 8)
s[i]-s[i-1] <= cnt[i] (1 <= i <= 23)
s[i]-s[i-1] >= 0 (1 <= i <= 23)
s[23]-s[0] >= ans
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
using namespace std;
const int INF = 0x3f3f3f3f;
const int Maxn = 1000+10;
struct Edge {
int v, w, next;
} edge[Maxn*Maxn];
int h[Maxn], edge_cnt, n;
int d[Maxn], cnt[Maxn], ct[30], x[30];
bool vis[Maxn];
void add(int u, int v, int w) {
edge[edge_cnt].v = v;
edge[edge_cnt].w = w;
edge[edge_cnt].next = h[u];
h[u] = edge_cnt++;
}
bool spfa(int s) {
for(int i = 0; i <= 24; ++i) {
vis[i] = false; d[i] = -INF;
cnt[i] = 0;
}
vis[s] = true; d[s] = 0;
queue <int> qu;
qu.push(s);
while(!qu.empty()) {
int u = qu.front(); qu.pop();
vis[u] = false;
for(int i = h[u]; i != -1; i = edge[i].next) {
Edge e = edge[i];
if(d[e.v] < d[u]+e.w) {
d[e.v] = d[u]+e.w;
if(!vis[e.v]) {
vis[e.v] = true;
if(++cnt[e.v] > 25) return false;
qu.push(e.v);
}
}
}
}
if(d[24] != -INF) return true;
else return false;
}
bool ok(int ans) {
memset(h, -1, sizeof(h));
edge_cnt = 0;
for(int i = 0; i < 24; ++i) {
add(i+1, i, -ct[i]);
add(i, i+1, 0);
}
for(int i = 1; i <= 24; ++i) {
if(i >= 8) add(i-8, i, x[i]);
else add(16+i, i, x[i]-ans);
}
add(0, 24, ans);
if(spfa(0)) return true;
else return false;
}
int main(void)
{
int T;
scanf("%d", &T);
while(T--) {
for(int i = 1; i <= 24; ++i) scanf("%d", &x[i]);
scanf("%d", &n);
memset(ct, 0, sizeof(ct));
int tmp;
for(int i = 0; i < n; ++i) {
scanf("%d", &tmp);
ct[tmp]++;
}
int L = 0, R = n, mid;
if(!ok(R)) printf("No Solution\n");
else {
while(L < R) {
mid = (L+R)/2;
if(ok(mid)) R = mid;
else L = mid+1;
}
printf("%d\n", R);
}
}
}
浙公网安备 33010602011771号