差分约束算法

本文解析了如何通过差分约束将问题转化为最短路或最长路的形式,介绍了超级源点的应用,包括判断负环、寻找解组和区间答案计算,涉及实例如糖果分配、工程规划和种树问题。展示了如何通过spfa算法实现求解过程并举例代码。
摘要由CSDN通过智能技术生成

来自算法竞赛进阶指南的讲解

在这里插入图片描述
x − y < = k x-y<=k xy<=k 形式可以转化为 x < = y + k x <= y + k x<=y+k x x x 会变得尽量小,和最短路一样, y y y 可以用 k k k 去更新 x x x,因此建边

add(y, x, k);

x − y > = k x-y>=k xy>=k,可以写成 x > = y + k x>=y+k x>=y+k y y y 会变得尽量大,和最长路一样, y y y 可以用 k k k 更新 x x x,因此建边

add(y, x, k)

当然转化为最短路和最长路并不是固定的,二者可以相互转化
x − y > = k x-y>=k xy>=k 可以写成 y < = x − k y<=x-k y<=xk,就变成了最短路形式,建边代码如下

add(x, y, -k)

一般设超级源点主要是为了判断是否存在解,即是否存在负环

  1. 题目要求输出一组解的时候,从虚拟节点开始跑即可
  2. 有时候需要输出 n n n 1 1 1 大多少,或者区间 [ 1 , n ] [1,n] [1,n] 的答案,求这个答案一般是从 1 1 1 开始跑
  3. 判断是否存在解时也要从虚拟节点开始跑

P5960 【模板】差分约束算法
思路:
题解
建立超级源点,将超级源点向所有边连一条长度为 0 0 0 的边, s p f a spfa spfa 跑最短路
如果有负环则无解
否则超级源点到所有点的最短路即为一组解, d i s [ i ] dis[i] dis[i] 即为 x i x_i xi
得到 x i < = 0 x_i<=0 xi<=0 所有 x x x 的最大解
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define mem(x, d) memset(x, d, sizeof(x))
#define eps 1e-6
using namespace std;
const int maxn = 2e6 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
struct node{
	int next, to, w;
}e[maxn];
int head[maxn], cnt;
int dis[maxn], vis[maxn], num[maxn];

void add(int x, int y, int z){
	e[++cnt].to = y;
	e[cnt].w = z;
	e[cnt].next = head[x];
	head[x] = cnt;
}
bool spfa(int s){
	queue <int> q;
	mem(dis, 0x3f);
	dis[s] = 0;q.push(s);
	while(!q.empty()){
		int x = q.front();q.pop();vis[x] = 0;
		for(int i = head[x]; i; i = e[i].next){
			int to = e[i].to;
			if(dis[to] > dis[x] + e[i].w){
				dis[to] = dis[x] + e[i].w;
				if(!vis[to]){
					vis[to] = 1;
					q.push(to);
					++num[to];
					if(num[to] > n + 1) return 0;
				}
			}
		}
	}
	return 1;
}
void work()
{
	cin >> n >> m;
	for(int i = 1; i <= n; ++i) add(0, i, 0);//建超级源点
	for(int i = 1; i <= m; ++i){
		int x, y, z;cin >> x >> y >> z;
		add(y, x, z);
	}
	if(!spfa(0)) cout << "NO";
	else {
		for(int i = 1; i <= n; ++i)
			cout << dis[i] << " ";
	}
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

类似的,建好图 s p f a spfa spfa 跑最长路,如果存在正环则无解
得到 x i > = 0 x_i>=0 xi>=0 所有 x x x 的最小解
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define mem(x, d) memset(x, d, sizeof(x))
#define eps 1e-6
using namespace std;
const int maxn = 2e6 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
struct node{
	int next, to, w;
}e[maxn];
int head[maxn], cnt;
int dis[maxn], vis[maxn], num[maxn];

void add(int x, int y, int z){
	e[++cnt].to = y;
	e[cnt].w = z;
	e[cnt].next = head[x];
	head[x] = cnt;
}
bool spfa(int s){
	queue <int> q;
	mem(dis, -1);
	dis[s] = 0;num[s] = 1;q.push(s);
	while(!q.empty()){
		int x = q.front();q.pop();vis[x] = 0;
		for(int i = head[x]; i; i = e[i].next){
			int to = e[i].to;
			if(dis[to] < dis[x] + e[i].w){
				dis[to] = dis[x] + e[i].w;
				if(!vis[to]){
					vis[to] = 1;
					q.push(to);
					++num[to];
					if(num[to] > n + 1) return 0;
				}
			}
		}
	}
	return 1;
}
void work()
{
	cin >> n >> m;
	for(int i = 1; i <= n; ++i) add(0, i, 0);//建超级源点
	for(int i = 1; i <= m; ++i){
		int x, y, z;cin >> x >> y >> z;
		add(x, y, -z);
	}
	if(!spfa(0)) cout << "NO";
	else {
		for(int i = 1; i <= n; ++i)
			cout << dis[i] << " ";
	}
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

4247. 糖果
思路:
不用建立超级源点
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define mem(x, d) memset(x, d, sizeof(x))
#define eps 1e-6
using namespace std;
const int maxn = 2e6 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
struct node{
	int next, to, w;
}e[maxn];
int head[maxn], cnt;
int dis[maxn];
bool vis[maxn];

void add(int x, int y, int z){
	e[++cnt].to = y;
	e[cnt].w = z;
	e[cnt].next = head[x];
	head[x] = cnt;
}
void spfa(int s){
	queue <int> q;
	mem(dis, 0x3f);
	dis[s] = 0;q.push(s);
	while(!q.empty()){
		int x = q.front();q.pop();vis[x] = 0;
		for(int i = head[x]; i; i = e[i].next){
			int to = e[i].to;
			if(dis[to] > dis[x] + e[i].w){
				dis[to] = dis[x] + e[i].w;
				if(!vis[to]){
					vis[to] = 1;
					q.push(to);
				}
			}
		}
	}
}
void work()
{
	cin >> n >> m;
	//for(int i = 1; i <= n; ++i) add(0, i, 0);
	for(int i = 1, x, y, z; i <= m; ++i){
		cin >> x >> y >> z;add(x, y, z);
	}
	spfa(1);
	cout << dis[n] << " ";	
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

P6145 [USACO20FEB]Timeline G
思路:
建立超级源点到每个节点的边,边权即为节点最早出现的时间
然后建图跑拓扑排序求最长路即可,最后输出一组解
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define mem(x, d) memset(x, d, sizeof(x))
#define eps 1e-6
using namespace std;
const int maxn = 2e6 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
struct node{
	int next, to, w;
}e[maxn];
int head[maxn], cnt;
int dis[maxn];
ll c;
int in[maxn];

void add(int x, int y, int z){
	e[++cnt].to = y;
	e[cnt].w = z;
	e[cnt].next = head[x];
	head[x] = cnt;
}
void topsort(){
	queue <int> q;
	q.push(0);
	while(!q.empty()){
		int x = q.front();q.pop();
		for(int i = head[x]; i; i = e[i].next){
			int to = e[i].to;
			in[to]--;
			if(!in[to]) q.push(to);
			dis[to] = max(dis[to], dis[x] + e[i].w);
		}
	}
}
void work()
{
	cin >> n >> m >> c;
	for(int i = 1; i <= n; ++i) {
		int x;cin >> x;add(0, i, x);//建超级源点
		in[i]++;
	}
	for(int i = 1; i <= c; ++i){
		int x, y, z;cin >> x >> y >> z;
		add(x, y, z);in[y]++;
	}
	topsort();
	for(int i = 1; i <= n; ++i) cout << dis[i] << endl;
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

P1250 种树
思路:
这个题问的是输出满足所有不等式的前提下,种树最少,其实就是最小解,思考方向就是跑最长路了
这个题的条件是约束区间和,显然可以用前缀和的两个端点约束,差分约束的题目一般都会有隐含的约束条件,需要注意。这样建完图后其实就是求区间 1 1 1 n n n 的前缀和,也就是从 0 0 0 开始跑长路,这个题中 0 0 0 节点是要用来维护前缀和的,不能作为虚拟节点。最后答案即为 d i s [ n ] dis[n] dis[n]

[ b , e ] [b,e] [b,e] 区间内树的数量至少为 c c c,转化为 s u m [ e ] − s u m [ b − 1 ] > = c sum[e]-sum[b-1]>=c sum[e]sum[b1]>=c
每个位置看成节点,那么即为 b − 1 b-1 b1 e e e 连一条长度为 c c c 的边
又因为每个位置至多只能种一棵树
因此还需要满足 s u m [ i ] − s u m [ i − 1 ] < = 1 sum[i]-sum[i-1]<=1 sum[i]sum[i1]<=1 以及 s u m [ i ] − s u m [ i − 1 ] > = 0 sum[i]-sum[i-1]>=0 sum[i]sum[i1]>=0
最长路建边即为

add(i, i - 1, -1);add(i - 1, i, 0);

跑最长路,虚拟节点连节点时边权设 i n f inf inf,看题目的边权范围,这个题设 1 0 5 10^5 105 就行
博主有解释原因,但是不是非常能理解
因此如果需要求最长路,我们可以先转化为最长路的形式,然后对边权取负求最短路,最后对答案再取负即可,这样就不用考虑虚拟节点连边的权值问题

code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define mem(x, d) memset(x, d, sizeof(x))
#define eps 1e-6
using namespace std;
const int maxn = 2e6 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
struct node{
	int next, to, w;
}e[maxn];
int head[maxn], cnt;
int dis[maxn], vis[maxn];

void add(int x, int y, int z){
	e[++cnt].to = y;
	e[cnt].w = z;
	e[cnt].next = head[x];
	head[x] = cnt;
}
void spfa(int s){
	queue <int> q;
	for(int i = 1; i <= n; ++i) dis[i] = -1e9;
	dis[s] = 0;q.push(s);
	while(!q.empty()){
		int x = q.front();q.pop();
		vis[x] = 0;
		for(int i = head[x]; i; i = e[i].next){
			int to = e[i].to;
			if(dis[to] < dis[x] + e[i].w){
				dis[to] = dis[x] + e[i].w;
				if(!vis[to]){
					vis[to] = 1;q.push(to);
				}
			}
		}
	}
}
void work()
{
	cin >> n >> m;
	for(int i = 1; i <= n; ++i) add(i - 1, i , 0), add(i, i - 1, -1);
	for(int i = 1; i <= m; ++i){
		int x, y, z;cin >> x >> y >> z;
		add(x - 1, y, z);
	}
	for(int i = 0; i <= n; ++i) add(n + 1, i, 1e5);
	spfa(n + 1);
	cout << dis[n] - 1e5;
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

这个代码大体思路和上边的一样,只不过把边权取负,然后跑最短路(虚拟节点连其他点边权为 0 0 0),最后对答案取负就好了
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define mem(x, d) memset(x, d, sizeof(x))
#define eps 1e-6
using namespace std;
const int maxn = 2e6 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
struct node{
	int next, to, w;
}e[maxn];
int head[maxn], cnt;
int dis[maxn], vis[maxn];

void add(int x, int y, int z){
	e[++cnt].to = y;
	e[cnt].w = z;
	e[cnt].next = head[x];
	head[x] = cnt;
}
void spfa(int s){
	queue <int> q;
	mem(dis, 0x3f);
	dis[s] = 0;q.push(s);
	while(!q.empty()){
		int x = q.front();q.pop();
		vis[x] = 0;
		for(int i = head[x]; i; i = e[i].next){
			int to = e[i].to;
			if(dis[to] > dis[x] + e[i].w){
				dis[to] = dis[x] + e[i].w;
				if(!vis[to]){
					vis[to] = 1;q.push(to);
				}
			}
		}
	}
}
void work()
{
	cin >> n >> m;
	for(int i = 1; i <= n; ++i) add(i - 1, i , 0), add(i, i - 1, 1);
	for(int i = 1; i <= m; ++i){
		int x, y, z;cin >> x >> y >> z;
		add(x - 1, y, -z);
	}
	for(int i = 0; i <= n; ++i) add(n + 1, i, 0);
	spfa(n + 1);
	cout << -dis[n];
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

P1260 工程规划
思路:
判断是否存在负环,存在则无解
如果有解,根据题目约束跑最短路即可,最后维护出 M i n = m i n ( d i s [ 1 ] , d i s [ 2 ] , . . . d i s [ n ] ) Min=min(dis[1],dis[2],...dis[n]) Min=min(dis[1],dis[2],...dis[n])
最后的 d i s [ i [ = d i s [ i ] − M i n dis[i[=dis[i]-Min dis[i[=dis[i]Min,输出一组解
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define mem(x, d) memset(x, d, sizeof(x))
#define eps 1e-6
using namespace std;
const int maxn = 2e6 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
struct node{
	int next, to, w;
}e[maxn];
int head[maxn], cnt;
int dis[maxn], vis[maxn], num[maxn];

void add(int x, int y, int z){
	e[++cnt].to = y;
	e[cnt].w = z;
	e[cnt].next = head[x];
	head[x] = cnt;
}
void spfa(int s){
	queue <int> q;
	mem(dis, 0x3f);
	dis[s] = 0;q.push(s);num[s]++;
	while(!q.empty()){
		int x = q.front();q.pop();
		vis[x] = 0;
		for(int i = head[x]; i; i = e[i].next){
			int to = e[i].to;
			if(dis[to] > dis[x] + e[i].w){
				dis[to] = dis[x] + e[i].w;
				if(!vis[to]){
					vis[to] = 1;q.push(to);++num[to];
					if(num[to] > n){
						cout << "NO SOLUTION";return;
					}
				}
			}
		}
	}
	int Min = inf;
	for(int i = 1; i <= n; ++i) Min = min(Min, dis[i]);
	for(int i = 1; i <= n; ++i) cout << dis[i] - Min << endl;
}
void work()
{
	cin >> n >> m;
	for(int i = 1; i <= n; ++i) add(0, i, 0);
	for(int i = 1; i <= m; ++i){
		int x, y, z;cin >> x >> y >> z;
		add(y, x, z);
	}
	spfa(0);
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

P4878 [USACO05DEC]Layout G
思路:
转化一下题目问题:
存在负权环则无解
1 1 1 n n n 不连通,则输出 − 2 -2 2
存在解,因为是要求最远距离,输出 1 1 1 n n n最短路即可
先从 0 0 0 跑一遍判负环
然后从 1 1 1 跑一遍求答案
ps:
想了半天为什么相邻节点要建边权为 0 0 0 的边
突然发现题目要求牛牛之间的排队要按照编号来,并且不同编号的牛可以在同一个位置
设两个牛牛的编号为 i , j i,j i,j,那么必须满足 i < j i<j i<j 时, x i < = x j x_i<=x_j xi<=xj,即 x i < = x j + 0 x_i<=x_j+0 xi<=xj+0
这是隐含的约束条件
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define mem(x, d) memset(x, d, sizeof(x))
#define eps 1e-6
using namespace std;
const int maxn = 2e6 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
struct node{
	int next, to, w;
}e[maxn];
int head[maxn], cnt, num[maxn];
bool vis[maxn];
ll dis[maxn];

void add(int x, int y, int z){
	e[++cnt].to = y;
	e[cnt].w = z;
	e[cnt].next = head[x];
	head[x] = cnt;
}
int spfa(int s){
	queue <int> q;mem(dis, 0x3f);dis[s] = 0;
	q.push(s);
	while(!q.empty()){
		int x = q.front();q.pop();
		vis[x] = 0;
		for(int i = head[x]; i; i = e[i].next){
			int to = e[i].to;
			if(dis[to] > dis[x] + e[i].w){
				dis[to] = dis[x] + e[i].w;
				if(!vis[to]){
					++num[to];vis[to] = 1;q.push(to);
					if(num[to] > n) return -1;
				}
			}
		}
	}
	return (dis[n] == INF ? -2 : dis[n]);
}
void work()
{
	int ml, md;
	cin >> n >> ml >> md;
	for(int i = n; i >= 2; --i) add(i, i - 1, 0);
	for(int i = 1; i <= ml; ++i){// 题目保证了 x < y,根据题意如果不保证就需要交换一下
		int x, y, z;cin >> x >> y >> z;if(x > y) swap(x, y);
		add(x, y, z);// y - x <= z
	}
	for(int i = 1; i <= md; ++i){
		int x, y, z;cin >> x >> y >> z;if(x > y) swap(x, y);
		add(y, x, -z);// y - x >= z ---> x - y <= -z
	}
	for(int i = 1; i <= n; ++i) add(0, i, 0);
	if(spfa(0) == -1) cout << -1 << endl;
	else{
		cout << spfa(1);
	}
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

P1993 小 K 的农场
思路:
这个题纯纯模板,啥坑也没有
判断是否存在合法解,根据约束关系建图,建超级源点判负环, s f p a sfpa sfpa 跑最短路就好了

P2294 [HNOI2005]狡猾的商人
思路:
题解
这个题和种树类似,都是前缀和
但是这个题约束区间和的是等式,而非不等式
s u m [ t ] − s u m [ s − 1 ] = x sum[t]-sum[s-1]=x sum[t]sum[s1]=x 转化为 s u m [ t ] − s u m [ s − 1 ] > = x sum[t]-sum[s-1]>=x sum[t]sum[s1]>=x s u m [ t ] − s u m [ s − 1 ] < = x sum[t]-sum[s-1]<=x sum[t]sum[s1]<=x
然后建超级源点 n + 1 n+1 n+1,跑最短路判负环就好了,因为跑最短路超级源点连边的权值可以为 0 0 0,而最长路有时候设 0 0 0 不行
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define mem(x, d) memset(x, d, sizeof(x))
#define eps 1e-6
using namespace std;
const int maxn = 4e3 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
struct node{
	int next, to, w;
}e[maxn];
int head[maxn], cnt;
int dis[maxn], vis[maxn], num[maxn];

void add(int x, int y, int z){
	e[++cnt].to = y;
	e[cnt].w = z;
	e[cnt].next = head[x];
	head[x] = cnt;
}
string spfa(int s){
	queue <int> q;
	mem(dis, 0x3f);dis[s] = 0;q.push(s);
	while(!q.empty()){
		int x = q.front();q.pop();
		vis[x] = 0;
		for(int i = head[x]; i; i = e[i].next){
			int to = e[i].to;
			if(dis[to] > dis[x] + e[i].w){
				dis[to] = dis[x] + e[i].w;
				if(!vis[to]){
					vis[to] = 1;q.push(to);++num[to];
					if(num[to] > n) return "false";
				}
			}
		}
	}
	return "true";
}
void init(){
	mem(head, 0);cnt = 0;mem(vis, 0);mem(num, 0);
}
void work()
{
	cin >> n >> m;
	init();
	for(int i = 1; i <= m; ++i){
		int s, t, v;cin >> s >> t >> v;
		add(s - 1, t, v);add(t, s - 1, -v);
	}
	for(int i = 0; i <= n; ++i) add(n + 1, i, 0);
	cout << spfa(n + 1) << endl;
}

int main()
{
	ios::sync_with_stdio(0);
	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

3275 [SCOI2011]糖果
思路:
这个题数据不强, s p f a spfa spfa 可以过
连通性博客
这个博客里银河这道题数据强(虽然标的数据强,其实挺弱的,spfa仍然能过

这个题主要是建图,上边博客也有写,这里不就写了

SP116 INTERVAL - Intervals
思路:
因为它要求取的整数互不相同,所以几乎和前边的种树一模一样,就是输入的节点范围不是 [ 1 , n ] [1,n] [1,n],稍微一下种树代码就好了
注意:这道题显然应该跑最长路,我的写法只不过把所有权值取负,转化成跑最短路

362. 区间
和这个题一样,但是数据稍微强一些,区间左端点会取到 0 0 0,这区间时候需要往右移动一位
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define mem(x, d) memset(x, d, sizeof(x))
#define eps 1e-6
using namespace std;
const int maxn = 2e5 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
struct node{
	int next, to, w;
}e[maxn];
int head[maxn], cnt;
int dis[maxn], vis[maxn];

void add(int x, int y, int z){
	e[++cnt].to = y;
	e[cnt].w = z;
	e[cnt].next = head[x];
	head[x] = cnt;
}
void init(){
	mem(vis, 0);mem(head, 0);cnt = 0;
}
void spfa(int s){
	queue <int> q;
	mem(dis, 0x3f);
	dis[s] = 0;q.push(s);
	while(!q.empty()){
		int x = q.front();q.pop();
		vis[x] = 0;
		for(int i = head[x]; i; i = e[i].next){
			int to = e[i].to;
			if(dis[to] > dis[x] + e[i].w){
				dis[to] = dis[x] + e[i].w;
				if(!vis[to]){
					vis[to] = 1;q.push(to);
				}
			}
		}
	}
}
void work()
{
	cin >> n; 
	init();
	int s = 5e4 + 2;
	for(int i = 1; i <= s - 1; ++i) add(i - 1, i , 0), add(i, i - 1, 1);
	int Max = 0;
	for(int i = 1; i <= n; ++i){
		int x, y, z;cin >> x >> y >> z;
		++x,++y;// 都向右移一位就好了
		add(x - 1, y, -z);
		Max = max(Max, y);
	}
	for(int i = 0; i <= s - 1; ++i) add(s, i, 0);
	spfa(s);
	cout << -dis[Max] << endl;
}

int main()
{
	ios::sync_with_stdio(0);
	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

393. 雇佣收银
思路:
博主讲的挺好
d [ i ] d[i] d[i] : 代表 i i i 时刻的收银员人数
r [ i ] r[i] r[i] 代表 i i i 时候至少需要的收银员人数
d i s [ i ] dis[i] dis[i]:代表 0 ∼ i 0\sim i 0i 这个时间段收银员的人数。(前缀和数组)

暂时不是非常能理解
每个位置能选的人数应该大于等于 0 0 0 并且小于等于最多的人数,即 d i s [ i ] − d i s [ i − 1 ] > = 0 dis[i]-dis[i-1]>=0 dis[i]dis[i1]>=0 并且 d i s [ i ] − d i s [ i − 1 ] < = d [ i ] dis[i]-dis[i-1]<=d[i] dis[i]dis[i1]<=d[i]
i > = 8 i>=8 i>=8 时, i i i 时刻的收银人数应该大于所需要的人数,即 d i s [ i ] − d i s [ i − 8 ] > = r [ i ] dis[i]-dis[i-8]>=r[i] dis[i]dis[i8]>=r[i]
1 < = i < = 7 1<=i<=7 1<=i<=7 时, d i s [ i ] + d i s [ 24 ] − d i s [ 16 + i ] > = r [ i ] dis[i]+dis[24]-dis[16+i]>=r[i] dis[i]+dis[24]dis[16+i]>=r[i]
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define mem(x, d) memset(x, d, sizeof(x))
#define eps 1e-6
using namespace std;
const int maxn = 100 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
struct node{
	int next, to, w;
}e[maxn];
int head[maxn], cnt, vis[maxn];
int dis[maxn], num[maxn], d[maxn];
int r[30];

void add(int x, int y, int z){
	e[++cnt].to = y;
	e[cnt].w = z;
	e[cnt].next = head[x];
	head[x] = cnt;
}
bool spfa(int s24){
	mem(head, 0);cnt = 0;
	d[0] = 0;
	for(int i = 1; i <= 24; ++i){
		add(i - 1, i, 0);add(i, i - 1, d[i]);
		if(i >= 8) add(i - 8, i, -r[i]);
		else add(i + 16, i, s24 - r[i]);
	}
	add(0, 24, -s24);add(24, 0, s24);
	queue<int> q;
	mem(dis, 0x3f);mem(vis, 0);mem(num, 0);
	dis[0] = 0;q.push(0);
	while(!q.empty()){
		int x = q.front();q.pop();vis[x] = 0;
		for(int i = head[x]; i; i = e[i].next){
			int to = e[i].to;
			if(dis[to] > dis[x] + e[i].w){
				dis[to] = dis[x] + e[i].w;
				if(!vis[to]){
					vis[to] = 1;++num[to];q.push(to);
					if(num[to] >= 25) return 0;
				}
			}
		}
	}
	return 1;
}
void work()
{
	for(int i = 1; i <= 24; ++i) cin >> r[i];
	cin >> n;
	for(int i = 1, x; i <= n; ++i){
		cin >> x;d[++x]++;
	}
	bool f = 0;
	int l = 0, r = n;
	while(l < r){
		int mid = l + r >> 1;
		if(spfa(mid)) r = mid;
		else l = mid + 1; 
	}
	if(spfa(l)) cout << l << endl;
	else cout << "No Solution" << endl;
}

int main()
{
	ios::sync_with_stdio(0);
	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值