差分约束系统其实就是将不等式组的求解问题转化为最短路进行求解,所以属于图论。但往往抽象出不等式组是不容易的。
差分约束系统入门可看这位大佬的博文:夜深人静写算法(四)
本题大意:n个区间,输入n行ai,bi,ci,代表在区间[ai, bi]上至少要选择ci个整数点,可以在区间内任意取ci个不重复的点。求包括所有区间的区间内至少要取多少个整数点。
思路:用d[i]表示从0到i至少要取d[i]个点,可抽象出d[-1]=0,所以[ai, bi]至少要取ci就可以抽象为d[bi] - d[ai-1] >= ci; (闭区间,所以需要减d[ai-1])。下标是从-1开始的,所以写程序的时候我们需要整体右移一位。
上述是一个约束条件。又因为每个点要么取要么不取,所以得到下面的约束条件:
d[i] - d[i-1] >= 0;
d[i] - d[i-1] <= 1;
不等式既有 >= 又有 <= ,所以需要进行不等式标准化,转换之后便可以求最短路了,至此,所有约束条件利用完毕。转换为 <= 是一种做法,转换为 >= 又是另一种做法,有点细微的区别。
先看下两种代码:
Code1:
#include <algorithm> #include <iostream> #include <string.h> #include <cstdio> #include <queue> using namespace std; const int inf = 0x3f3f3f3f; const int maxn = 50005; const int maxm = maxn*3; struct node { int v, w, next; }edge[maxm]; int no, head[maxn]; int vis[maxn], dis[maxn], cnt[maxn]; queue<int> q; int low, up; inline void init() { no = 0; memset(head, -1, sizeof head); } inline void add(int u, int v, int w) { edge[no].v = v; edge[no].w = w; edge[no].next = head[u]; head[u] = no++; } int SPFA(int S, int T) { memset(vis, 0, sizeof vis); fill(dis+low, dis+up+1, -inf); memset(cnt, 0, sizeof cnt); while(!q.empty()) q.pop(); dis[S] = 0; q.push(S); vis[S] = 1; while(!q.empty()) { int u = q.front(); q.pop(); vis[u] = 0; ++cnt[u]; if(cnt[u] > up-low+1) return -1; for(int k = head[u]; k != -1; k = edge[k].next) { int v = edge[k].v; if(dis[v] < dis[u]+edge[k].w) { dis[v] = dis[u]+edge[k].w; if(!vis[v]) vis[v] = 1, q.push(v); } } } return dis[T]; } int main() { //freopen("in.txt", "r", stdin); int n, a, b, c; scanf("%d", &n); init(); low = inf, up = 0; for(int i = 1; i <= n; ++i) { scanf("%d %d %d", &a, &b, &c); low = min(low, a); up = max(up, b); add(a, b+1, c); } for(int i = low; i <= up; ++i) add(i, i+1, 0), add(i+1, i, -1); printf("%d\n", SPFA(low, up+1)); return 0; }
Code2:
#include <algorithm> #include <iostream> #include <string.h> #include <cstdio> #include <queue> using namespace std; const int inf = 0x3f3f3f3f; const int maxn = 50005; struct node { int v, w, next; }edge[maxn*3]; queue<int> q; int n, no, MIN, MAX; int head[maxn], dis[maxn], vis[maxn]; inline void init() { no = 0; memset(vis, 0, sizeof vis); memset(head, -1, sizeof head); memset(dis, 0x3f, sizeof dis); } inline void add(int u, int v, int w) { edge[no].v = v; edge[no].w = w; edge[no].next = head[u]; head[u] = no++; } void SPFA(int s, int t) { while(!q.empty()) q.pop(); q.push(s); dis[s] = 0; vis[s] = 1; while(!q.empty()) { int tp = q.front(); q.pop(); vis[tp] = 0; int k = head[tp]; while(k != -1) { if(dis[edge[k].v] > dis[tp] + edge[k].w) { dis[edge[k].v] = dis[tp] + edge[k].w; if(!vis[edge[k].v]) { vis[edge[k].v] = 1; q.push(edge[k].v); } } k = edge[k].next; } } } int main() { int a, b, c; scanf("%d", &n); init(); MIN = inf, MAX = -inf; for(int i = 1; i <= n; ++i) { scanf("%d %d %d", &a, &b, &c); add(b+1, a, -c); MIN = min(MIN, a); MAX = max(MAX, b+1); } for(int i = MIN; i < MAX; ++i) { add(i, i+1, 1); add(i+1, i, 0); } SPFA(MAX, MIN); printf("%d\n", -dis[MIN]); return 0; }
现在来看看两者的区别:第一个是通过转换为 >=不等式组进行建图的,所以需要求建出图的最长路径,需要将dis
初始化为-inf。第二个则是 <=, 求最短路径, 将dis初始化为inf。还有一个不同是两者进行求解路径的方向是不
同的,一个(S->T),一个(T->S),需要选择正确的求解路径方向才能得到正确ans。其实选择求解路径的方向是根
据建图的'主方向'确定的,且需要自行判断哪个求解方向取负才能得到正确答案(个人理解,毕竟这是我做的第一个差分约束题...),不同的题或许不同。
实际上正解是去求最长路,若干日子之后的新理解:不管求解两个变量差的最大值还是最小值,都可以转为最短路或者最长路去做,注意细节就好。比如用最短路去求,求解最大值时,正是正确方向。求解最小值时,则建的图恰好是求最长路的反向图+反向权值,然后再交换始终点即可。
继续加油~