差分约束把数学不等式求最大值最小值问题转变成了图论中的最长路最短路问题,我觉得还是十分神奇的,当然刚开始还是十分懵逼的,对于建图的细节还是需要想一想的,目前看到的差分约束的题目大多变化都是在如何建边上需要好好考虑的。建图之后若发现负环或者正环就说明当前不等式组不可解,也有可能约束条件不够,答案点并没有被约束到,无穷大或无穷小,通常还会以一个超级源点作为出发点并且把向所有其他点发出一条权值为0的有向边,以保证了整张图的连通性,如果不这样做,可能以某个点出发,会导致某个特殊的连通块无法访问到,也就约束不到那些点了。
1.World Exhibition HDU - 3592
可以直接看出条件的差分约束题了,有些人的之间的距离不能大于一个值,有些人之间的具体不能小于一个值,然后就变成不等式,同一大于号小于号之后就能直接建图了,以小于号建边求最短路。
#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
const int maxe = 20055;
const int maxn = 1005;
int n,x,y;
int to[maxe], nex[maxe], val[maxe], head[maxn];
int s[maxn];
int tot;
void add(int a, int b, int v){
to[++tot] = b;
val[tot] = v;
nex[tot] = head[a];
head[a] = tot;
}
bool spfa(int beg,int endd){
memset(s, 0x3f, sizeof(s));
int mark[maxn] = { 0 };
int in[maxn] = { 0 };
s[beg] = 0;
mark[beg] = in[beg] = 1;
queue<int> que;
que.push(beg);
while (!que.empty()){
int cur = que.front();
que.pop();
mark[cur] = 0;
for (int k = head[cur]; k != 0; k = nex[k]){
int newp = to[k];
if (s[newp] > s[cur] + val[k]){
s[newp] = s[cur] + val[k];
if (!mark[newp]){
mark[newp] = 1;
if (++in[newp]>n)return 0;
que.push(newp);
}
}
}
}
return 1;
}
int main(){
int t,a,b,c;
scanf("%d", &t);
while (t--){
scanf("%d%d%d", &n, &x, &y);
memset(head, 0, sizeof(head));
tot = 1;
for (int i = 0; i < x; i++){
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
for (int i = 0; i < y; i++){
scanf("%d%d%d", &a, &b, &c);
add(b, a, -c);
}
if (spfa(1, n)){
if (s[n] == inf){
printf("-2\n");
}
else{
printf("%d\n", s[n]);
}
}
else{
printf("-1\n");
}
}
return 0;
}
2.King POJ - 1364
傻国王对数列的某个连续区间和给出一个大于x或者小于y的结果,给你一组这样的判断,问你能不能找出满足的数列。用+1-1把大于小于转变成大于等于和小于等于。用超级源点把图连通起来。
#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
const int maxe = 5005;
const int maxn = 200;
int n,m;
int to[maxe], nex[maxe], val[maxe], head[maxn];
int s[maxn];
int tot;
void add(int a, int b, int v){
to[++tot] = b;
val[tot] = v;
nex[tot] = head[a];
head[a] = tot;
}
bool spfa(int beg,int endd){
memset(s, -0x3f, sizeof(s));
int mark[maxn] = { 0 };
int in[maxn] = { 0 };
s[beg] = 0;
mark[beg] = in[beg] = 1;
queue<int> que;
que.push(beg);
while (!que.empty()){
int cur = que.front();
que.pop();
mark[cur] = 0;
for (int k = head[cur]; k != 0; k = nex[k]){
int newp = to[k];
if (s[newp] < s[cur] + val[k]){
s[newp] = s[cur] + val[k];
if (!mark[newp]){
mark[newp] = 1;
if (++in[newp]>n)return 0;
que.push(newp);
}
}
}
}
return 1;
}
char str[3];
int main(){
int t,a,b,c;
while (scanf("%d", &n),n){
memset(head, 0, sizeof(head));
scanf("%d", &m);
for (int i = 0; i < m; i++){
scanf("%d%d%s%d", &a, &b, str, &c);
if (str[0] == 'g'){
add(a , a + b+1, c + 1);
}
else{
add(a + b+1, a , 1 - c);
}
}
for (int i = 1; i <= n; i++)add(0, i, 0);
if (spfa(0,n)){
printf("lamentable kingdom\n");
}
else{ printf("successful conspiracy\n"); }
}
return 0;
}
3.Instrction Arrangement HDU - 4109
这题用拓扑排序也能做,或者差分约束找出开始时间最早的指令的时间,和最晚的指令,因为指令执行也要1ns,所以答案是max-min+1。
#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
const int maxe = 30005;
const int maxn = 1005;
int n,m;
int head[maxn], nex[maxe], to[maxe];
int val[maxe];
int tot;
int s[maxn];
void add(int a, int b, int c){
to[++tot] = b;
val[tot] = c;
nex[tot] = head[a];
head[a] = tot;
}
bool spfa(int beg){
memset(s, -0x3f, sizeof(s));
int mark[maxn] = { 0 };
int in[maxn] = { 0 };
s[beg] = 0;
mark[beg] = in[beg] = 1;
queue<int> que;
que.push(beg);
while (!que.empty()){
int cur = que.front();
que.pop();
mark[cur] = 0;
for (int k = head[cur]; k != 0; k = nex[k]){
int newp = to[k];
if (s[newp] < s[cur] + val[k]){
s[newp] = s[cur] + val[k];
if (!mark[newp]){
mark[newp] = 1;
if (++in[newp]>n)return 0;
que.push(newp);
}
}
}
}
return 1;
}
int main(){
int a, b, c;
while (~scanf("%d%d", &n, &m)){
memset(head, 0, sizeof(head));
tot = 1;
for (int i = 0; i < m; i++){
scanf("%d%d%d", &a, &b, &c);
a++, b++;
add(a, b, c);
}
for (int i = 1; i <= n; i++){
add(0, i, 0);
}
spfa(0);
int up = 0, down = inf;
for (int i = 1; i <= n; i++){
up = max(up, s[i]);
down = min(down, s[i]);
}
printf("%d\n", up-down+1);
}
return 0;
}
4.Burn the Linked Camp ZOJ - 2770
火烧连营,给出数列每个位置的最大值,和连续子序列和的最小值,要你求出数列总和的最小值。如果情报给出的连续子序列和的值大于上限值之和,建图就会出现一个正环,然后就会无限地走正环,就是无解地情况。
#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
const int maxe = 30005;
const int maxn = 1005;
int n,m;
int head[maxn], nex[maxe], to[maxe];
ll val[maxe];
int tot;
ll s[maxn];
void add(int a, int b, int c){
to[++tot] = b;
val[tot] = c;
nex[tot] = head[a];
head[a] = tot;
}
bool spfa(int beg){
memset(s, -0x3f, sizeof(s));
int mark[maxn] = { 0 };
int in[maxn] = { 0 };
s[beg] = 0;
mark[beg] = in[beg] = 1;
queue<int> que;
que.push(beg);
while (!que.empty()){
int cur = que.front();
que.pop();
mark[cur] = 0;
for (int k = head[cur]; k != 0; k = nex[k]){
int newp = to[k];
if (s[newp] < s[cur] + val[k]){
s[newp] = s[cur] + val[k];
if (!mark[newp]){
mark[newp] = 1;
if (++in[newp]>n)return 0;
que.push(newp);
}
}
}
}
return 1;
}
int main(){
int a, b, c;
while (~scanf("%d%d", &n, &m)){
memset(head, 0, sizeof(head));
tot = 1;
for (int i = 2; i <= n+1; i++){
scanf("%d", &a);
add(i, i - 1, -a);
}
for (int i = 0; i < m; i++){
scanf("%d%d%d", &a, &b, &c);
add(a, b+1, c);
}
for (int i = 1; i <= n + 1; i++){
add(0, i, 0);
}
if (spfa(0)){
printf("%lld\n", s[n + 1]);
}
else{
printf("Bad Estimations\n");
}
}
return 0;
}
Cashier Employment HDU - 1529
很综合的一道题,也比较难看出来建边的条件,有个不等式需要与三个点有关,就得枚举其中一个点,然后再来建图,就用了二分的方法。
#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
const int maxe = 1005;
const int maxn = 30;
int need[25];//x-1到x小时需要多少人
int n;
int s[25];//0-n小时total 雇佣
int have[25];//有多少人能在x-1小时被雇佣
int to[maxe], nex[maxe], val[maxe], head[maxn];
int tot;
void add(int a, int b, int v){
to[++tot] = b;
val[tot] = v;
nex[tot] = head[a];
head[a] = tot;
}
bool spfa(int beg,int endd,int mid){
for (int i = 0; i <= 24; i++){ s[i] = -0x3f3f3f3f; }
int mark[maxn] = { 0 };
int in[maxn] = { 0 };
s[beg] = 0;
mark[beg] = in[beg] = 1;
queue<int> que;
que.push(beg);
while (!que.empty()){
int cur = que.front();
que.pop();
mark[cur] = 0;
for (int k = head[cur]; k != 0; k = nex[k]){
int newp = to[k];
if (s[newp] < s[cur] + val[k]){
s[newp] = s[cur] + val[k];
if (!mark[newp]){
mark[newp] = 1;
if (++in[newp]>n)return 0;
que.push(newp);
}
}
}
}
if (s[24]==mid)
return 1;
return 0;
}
bool ok(int mid){
memset(head, 0, sizeof(head));
tot = 1;
for (int i = 1; i <= 24; i++){
add(i - 1, i, 0);
add(i, i - 1, -have[i]);
if (i >= 8)add(i - 8, i, need[i]);
else{ add(i + 16, i, need[i] - mid); }
}
add(0, 24, mid);
add(24, 0, -mid);
return spfa(0, 24,mid);
}
int main(){
int t,x;
scanf("%d", &t);
while (t--){
memset(have, 0, sizeof(have));
for (int i = 1; i <= 24; i++){ scanf("%d", &need[i]); }
scanf("%d", &n);
for (int i = 0; i < n; i++){
scanf("%d", &x);
have[x+1]++;
}
int ans = -1;
int lef = 0, rig = n;
while (lef<=rig){
int mid=(rig + lef) / 2;
if (ok(mid)){
ans = mid;
rig = mid-1; }
else{ lef = mid+1; }
}
if (ans==-1){ printf("No Solution\n"); }
else{ printf("%d\n", ans); }
}
return 0;
}