差分约束算法

1,定义

差分约束是由n个变量,m条不等式组成的n元一次不等式组,不等式的形式都是形如a-b<=c,要求判断是否有解

2,思路

差分约束可以用最短路或者最长路实现,两者思想与算法大同小异,后面会例题一会专门介绍。

我们发现,不等式形式与最短路的dis[v]<=dis[u]+w形式相似,如果我们对u-v<=c建立v->u的边,所有边建立后,只要不存在负环,最终一定会满足不等式组。

我们可以选一个点做起点,一般是自己起一个源点s,s向其他点连s->u(边权为0)的边,那么最终所有点到源点s的距离满足dis[u]<=s(dis[s]=0),每个点的dis[u]就是答案之一。

SPFA板子:最短路径三大算法——3,SPFA算法(复杂度O(n*m))

3,模板题:P5960 【模板】差分约束算法

#include <bits/stdc++.h>
using namespace std;
#define ll     long long
typedef unsigned long long ull;
typedef pair<long long, long long> pll;
typedef pair<int, int> pii;

//double 型memset最大127,最小128
//std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
const int INF = 0x3f3f3f3f;         //int型的INF
const ll llINF = 0x3f3f3f3f3f3f3f3f;//ll型的llINF
const int N = 5e3 + 10;

int n, m, dis[N], cnt[N], head[N], num;
bool vis[N];
struct node
{
	int next, to, t;
} edge[N * N];

void add(int u, int v, int w)
{
	edge[++num].next = head[u];
	edge[num].to = v;
	edge[num].t = w;
	head[u] = num;
}

bool spfa()
{
	memset(dis, 0x3f, sizeof(dis));
	dis[0] = 0;
	queue<int>q;
	q.push(0);
	while (!q.empty())
		{
			int u = q.front();
			q.pop();
			vis[u] = 0;
			for (int i = head[u]; i; i = edge[i].next)
				{
					int v = edge[i].to, w = edge[i].t;
					if (dis[v] > dis[u] + w)
						{
							dis[v] = dis[u] + w;
							cnt[v] = cnt[u] + 1;
							if (cnt[v] == n + 1)return 0;
							if (!vis[v])vis[v] = 1, q.push(v);
						}
				}
		}
	return 1;
}

int main()
{
	cin >> n >> m;
	int u, v, w;
	for (int i = 1; i <= n; ++i)add(0, i, 0);
	for (int i = 1; i <= m; ++i)
		{
			cin >> u >> v >> w;
			add(v, u, w);
		}
	if (spfa())
		{
			for (int i = 1; i <= n; ++i)cout << dis[i] << ' ';
		}
	else cout << "NO" << endl;

}

4,例题一:Intervals

思路:

1,我们设s[i]表示1~i中使用了s[i]个数,则对于(a,b,c)有s[b]-s[a-1]>=c(注意是a-1,因为s[a]包括a的使用,s[b]-s[a-1]就是a~b的使用数量

2,我们也应注意到:0<=s[i]-s[i-1]<=1.

3,我们讨论一下最长路与最短路算法与思路的区别,区间[l,r]

最短路形式:左边<=右边。

1,会发现,我们每次更新左边,其实都是让左边“尽可能大”,因为他明明可以取小于右边的值中众多一个,但是偏偏取值==右边(最大),所以如果我们按照正常思路spfa从l跑到r,答案一定是s[r]==r(符合条件的答案,但是不是最小,而是最大(笑))。

2,所以用最短路解决最小值问题,聪明的你一定明白正难则反,从r跑到l,即,我们每次用r更新了l时,都是让s[l]尽可能的大从而接近s[r],最后得出的答案s[r]-s[l-1]就会尽可能小(即最小值),一般设s[r]=0,答案就是-s[l-1]。

#include <bits/stdc++.h>
using namespace std;
#define ll     long long
typedef unsigned long long ull;
typedef pair<long long, long long> pll;
typedef pair<int, int> pii;

//double 型memset最大127,最小128
//std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
const int INF = 0x3f3f3f3f;         //int型的INF
const ll llINF = 0x3f3f3f3f3f3f3f3f;//ll型的llINF
const int N = 5e4 + 10;

int  head[N *3], dis[N];
bool vis[N];
int num, l, r;
struct node
{
	int next, to, w;
} edge[N *3];//最多建立N*3的边

void add(int u, int v, int w)
{
	edge[++num].next = head[u];
	edge[num].to = v;
	edge[num].w = w;
	head[u] = num;
}

void spfa()
{
	queue<int>q;
	memset(dis, 0x3f, sizeof(dis));
	memset(vis, 0, sizeof(vis));
	dis[r] = 0;//一般设dis[r]=0,答案就是-dis[l-1]
	q.push(r);
	while (!q.empty())
		{
			int u = q.front();
			q.pop();
			vis[u] = 0;
			for (int i = head[u]; i; i = edge[i].next)
				{
					int v = edge[i].to, w = edge[i].w;
					if (dis[v] > dis[u] + w)
						{
							dis[v] = dis[u] + w;
							if (!vis[v])vis[v] = 1, q.push(v);
						}
				}
		}

}

int main()
{
	std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int n, u, v, w;
	while (cin >> n)
		{
			memset(head, 0, sizeof(head));
			num = 0;
			l = INF, r = -INF;
			for (int i = 1; i <= n; ++i)
				{
					cin >> u >> v >> w;
					add(v, u - 1, -w);
					l = min(l, u), r = max(r, v);
				}
			for (int i = l; i <= r; ++i)add(i, i - 1, 0), add(i - 1, i, 1);
			spfa();
			cout << -dis[l - 1] << endl;//为什么dis[l-1]不是dis[l],我们说过dis[l]包括l,因为我们求解结果完整形式是s[r]-s[l-1],如果是s[l]对于l这个点不能用
		}
	return 0;
}

最长路形式:左边>=右边

最长路在求最小值方面就很容易思考了,因为>=,所以每次更新左边的时候,左边都是“尽可能小”,所以从l遍历到r,s[r]-s[l-1]就是答案了,一般设s[l-1]=0

(两者代码就是小改一下的区别)

#include <bits/stdc++.h>
using namespace std;
#define ll     long long
typedef unsigned long long ull;
typedef pair<long long, long long> pll;
typedef pair<int, int> pii;

//double 型memset最大127,最小128
//std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
const int INF = 0x3f3f3f3f;         //int型的INF
const ll llINF = 0x3f3f3f3f3f3f3f3f;//ll型的llINF
const int N = 5e4 + 10;

int  head[N * 3], dis[N];
bool vis[N];
int num, l, r;
struct node
{
	int next, to, w;
} edge[N * 3]; //最多建立N*3的边

void add(int u, int v, int w)
{
	edge[++num].next = head[u];
	edge[num].to = v;
	edge[num].w = w;
	head[u] = num;
}

void spfa()
{
	queue<int>q;
	memset(dis, -0x3f, sizeof(dis));
	memset(vis, 0, sizeof(vis));
	dis[l - 1] = 0; //从左边遍历到右边s[r]-s[l-1]就是答案了,一般设s[l-1]=0
	q.push(l - 1);
	while (!q.empty())
		{
			int u = q.front();
			q.pop();
			vis[u] = 0;
			for (int i = head[u]; i; i = edge[i].next)
				{
					int v = edge[i].to, w = edge[i].w;
					if (dis[v] < dis[u] + w)
						{
							dis[v] = dis[u] + w;
							if (!vis[v])vis[v] = 1, q.push(v);
						}
				}
		}

}

int main()
{
	std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int n, u, v, w;
	while (cin >> n)
		{
			memset(head, 0, sizeof(head));
			num = 0;
			l = INF, r = -INF;
			for (int i = 1; i <= n; ++i)
				{
					cin >> u >> v >> w;
					add(u - 1, v, w);
					l = min(l, u), r = max(r, v);
				}
			for (int i = l; i <= r; ++i)add(i, i - 1, -1), add(i - 1, i, 0);
			spfa();
			cout << dis[r] << endl;
		}
	return 0;
}

5,例题2:Cashier Employment

(虽然求最小值最长路比较简单,但是我最短路写多了,这里只写最短路,两者没什么区别的)

思路:

我们设s[i]表示1~i小时销售员总人数,a[i]表示要求的当天人数至少多少,b[i]表示从i时刻开始工作的员工人数:

容易得到的关系:

1,i>8:s[i]-s[i-8]>=a[i](不是s[i]-s[i-1],因为i-8及其前面的员工工作时间最多到i-1,所以s[i]-s[i-8]表示能够在i时间工作的人员总数。

2,i<=8:s[i]-s[i+16]+s[24]>=a[i](s[i+16]==s[i-8+24]),我们发现,跨天数,s[i]一定小于s[i+16]的,所以我们要补上一整天s[24]的值才成立不等式

3,0<=s[i]-s[i-1]<=b[i],因为第i天最多多来b[i]员工,所以不能比i-1天对于这个人数

 

1,我们注意到不等式混进脏东西了(s[24]),我们不知道他的值,因为s[24]就是我们要求的最小值呀!)。但是到这里暗示很明显了,二分s[24]最小值求解答案。

2,题目时间为0~23,我们改为1~24,0作为超级源点,因为差分图经常不连通,所以用他可以一次spfa跑所有点,令s[0]=0,那么每次二分都会限制s[24]-s[0]<=mid(答案)

#include <bits/stdc++.h>
using namespace std;
#define ll     long long
typedef unsigned long long ull;
typedef pair<long long, long long> pll;
typedef pair<int, int> pii;

//double 型memset最大127,最小128
//std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
const int INF = 0x3f3f3f3f;         //int型的INF
const ll llINF = 0x3f3f3f3f3f3f3f3f;//ll型的llINF
const int N = 30;

int dis[N], head[N << 2], a[N], b[N], cnt[N];
bool vis[N];
int num;
struct node
{
	int next, to, w;
} edge[N << 2];

void add(int u, int v, int w)
{
	edge[++num].next = head[u];
	edge[num].to = v;
	edge[num].w = w;
	head[u] = num;
}
void init()
{
	memset(head, 0, sizeof(head));
	memset(cnt, 0, sizeof(cnt));
	memset(dis, 0x3f, sizeof(dis));
	memset(vis, 0, sizeof(vis));
	num = 0;
}
//图不一定连通,所以建超级源点约束
bool spfa(int mid)
{
	queue<int>q;
	init();
	add(0, 24, mid);//限制dis[24]不能大于mid
	for (int i = 1; i <= 24; ++i)
		{
			add(i, i - 1, 0), add(i - 1, i, b[i]);
			if (i > 8)add(i, i - 8, -a[i]);
			else add(i, i + 16, mid - a[i]);
		}
	dis[0] = 0;
	q.push(0);
	while (!q.empty())
		{
			int u = q.front();
			q.pop();
			vis[u] = 0;
			for (int i = head[u]; i; i = edge[i].next)
				{
					int v = edge[i].to, w = edge[i].w;
					if (dis[v] > dis[u] + w)
						{
							dis[v] = dis[u] + w;
							if (!vis[v])
								{
									vis[v] = 1;
									q.push(v);
									cnt[v]++;
									if (cnt[v] > 25)return 0;
								}
						}
				}
		}
	if (mid == dis[24])//因为我们是要问这个mid可不可行,已经以mid==s[24]为前提建边,如果你最后跟我说s[24]他他他。。。变了,那肯定不可以呀
		return 1;
	else return 0;
}

int main()
{
	int t, n, x;
	cin >> t;
	while (t--)
		{
			memset(b, 0, sizeof(b));
			for (int i = 1; i <= 24; ++i)cin >> a[i];
			cin >> n;//员工总数
			for (int i = 1; i <= n; ++i)cin >> x, b[x + 1]++;
			int l = 0, r = n;
			int ans = INF;
			while (l <= r)
				{
					int mid = (l + r) >> 1;
					if (spfa(mid))
						{
							ans = mid, r = mid - 1;
						}
					else l = mid + 1;
				}
			if (ans < INF)cout << ans << endl;
			else cout << "No Solution" << endl;
		}
	return 0;
}

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值