题目链接

差分约束+二分答案

这道题和一般的差分约束有点不一样。 假设我们定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);
		}
	}
 }