差分约束系统(题目)

差分约束系统:

简介:https://blog.csdn.net/weixin_45012616/article/details/100880099

做法: https://www.cnblogs.com/cytus/p/9511604.html

 求解差分约束系统,可以转化成图论的单源最短路径问题。观察,会发现它类似最短路中的三角不等式,即。因此,以每个变数为结点,对于约束条件,连接一条边,边权为。再增加一个原点与所有定点相连,边权均为0。对这个图以s为原点运行Bellman-ford算法(或SPFA算法),最终即为一组可行解。

 经常出现的是i与i-1连接,或者i与i+1连接,应该是小的连向大的

题目:

1509:【例 1】Intervals

满足多个不等式的条件下求解某样东西时,可以把这些不等式转化成图论中求最短路的松弛公式然后
把问题转换成求解最短(长)路。这道题就是一个很标准的差分约束系统。
要求满足的不等式包括s[b[i]]-s[a[i]-1]≥c[i],0≤s[i]-s[i-1]≤1,其中s[i]表示到第i个位置为止选择的点的个数,转换一下,就可以得到:
s[b[i]]≥s[a[i]-1]+c[i],
s[i]≥s[i-1]+0,
s[i-1]≥s[i]+(-1),
这就得到了三种边权的边,就可以建图了。

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=5e4+10;
const int INF=0x3fffffff;
typedef long long LL;
//差分约束系统标准题
int head[maxn],dis[maxn],vis[maxn];
struct node{
	int to,dis,next;
}ed[maxn*2];
int cnt,n;
void add(int x,int y,int z){
	ed[++cnt].dis=z;
	ed[cnt].to=y;
	ed[cnt].next=head[x];
	head[x]=cnt;
}
queue<int> q;
void spfa(int st){
	for(int i=1;i<=n;i++) dis[i]=-INF;
	//因为是求最长路
	dis[st]=0;
	q.push(st);
	vis[st]=1;
	while(!q.empty()){
		int t=q.front();
		q.pop();
		vis[t]=0;
		for(int i=head[t];i;i=ed[i].next){
			int op=ed[i].to;
			if(dis[op]<dis[t]+ed[i].dis){  //最长路 
				dis[op]=dis[t]+ed[i].dis;
				if(!vis[op]){
					vis[op]=1;
					q.push(op);
				}
			}
		}
	} 
}
int main(){
	scanf("%d",&n);
	int x,y,z;
	int st=INF,ed=-INF;
	for(int i=1;i<=n;i++){
		scanf("%d %d %d",&x,&y,&z);
		add(x-1,y,z);  //建边
		st=min(st,x-1);  //记得减一 
		ed=max(ed,y); 
	}
	for(int i=st;i<=ed;i++){
		add(i-1,i,0);
		add(i,i-1,-1);
	}
	spfa(st);
	printf("%d
",dis[ed]);
return 0;
}

1510:【例 2】出纳员问题

https://blog.csdn.net/aiyunyan0969/article/details/102142702
//讲解: https://blog.csdn.net/weixin_43907802/article/details/100308865

这篇国集论文里面有,这篇论文很不错 数与图的完美结合-------浅析差分约束系统 华中师大一附中 冯威

设num[i] 为来应聘的在第i个小时开始工作的人数
r[i] 为第i个小时至少需要的人数
x[i] 为招到的在第i个小时开始工作的人数
根据题意有:
0 <= x[i] <= num[i]
x[i] + x[i-1] + …+ x[i-7] >= r[i] (题目中的连续工作8小时)
再设 s[i] = x[1] + … + x[i]
则有: s[i] – s[i-1] >= 0
s[i-1] – s[i] >= –num[i]
s[i] – s[i-8] >= r[i], 8 <= i <= 24
s[i] – s[i+16] >= r[i] – s[24], 1<= i <= 7
还需要添加一个隐藏不等式: s[24] – s[0] >= ans(枚举的答案)
通过枚举s[24],来检测是否满足条件(题目是求最小值,即求最长路,以0为源点),每次跑完spfa后,就看算出来的s[24]和枚举的是不是一样就行,因为spfa求出来的是当前约束条件下的
最小值,于是只要从小到大枚举ans和s[24]相等了就说明当前的最小值是ans

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
typedef long long LL;
//https://blog.csdn.net/aiyunyan0969/article/details/102142702
//讲解: https://blog.csdn.net/weixin_43907802/article/details/100308865
/*
这篇国集论文里面有,这篇论文很不错     数与图的完美结合-------浅析差分约束系统   华中师大一附中 冯威

设num[i] 为来应聘的在第i个小时开始工作的人数
r[i] 为第i个小时至少需要的人数
x[i] 为招到的在第i个小时开始工作的人数
根据题意有:
0 <= x[i] <= num[i]
x[i] + x[i-1] + …+ x[i-7] >= r[i] (题目中的连续工作8小时)
再设 s[i] = x[1] + … + x[i]
则有: s[i] – s[i-1] >= 0
s[i-1] – s[i] >= –num[i]
s[i] – s[i-8] >= r[i], 8 <= i <= 24
s[i] – s[i+16] >= r[i] – s[24], 1<= i <= 7
还需要添加一个隐藏不等式: s[24] – s[0] >= ans(枚举的答案)
通过枚举s[24],来检测是否满足条件(题目是求最小值,即求最长路,以0为源点),每次跑完spfa后,就看算出来的s[24]和枚举的是不是一样就行,因为spfa求出来的是当前约束条件下的
最小值,于是只要从小到大枚举ans和s[24]相等了就说明当前的最小值是ans
*/
struct node{
	int to;
	int len;
	int next;
}ed[500000];
int head[100000];
int r[10000];  //需要的人数
int t[10000]; //应征的人数
int s[10000]; //0---i雇佣的人数
int cnt,n,dis[10000];
queue<int> q;
void adde(int a,int b,int c){
	ed[++cnt].to=b;
	ed[cnt].len=c;
	ed[cnt].next=head[a];
	head[a]=cnt; 
}
void ju(int x){
	memset(head,-1,sizeof(head));
	adde(0,24,x);   //这一步 
	for(int i=1;i<=24;i++){
		adde(i-1,i,0);  ///连线
		adde(i,i-1,-t[i]);  //应征的人数 
	}
	for(int i=1;i<=16;i++){
		adde(i,i+8,r[i+8]);  //需要的人数 
	}
	for(int i=17;i<=24;i++){
		adde(i,i-16,r[i-16]-x);
	}
}
int vis[110],num[110];
int spfa(int x){
	while(!q.empty()) q.pop();
	for(int i=1;i<=25;i++) dis[i]=-INF;  //求最长路
	dis[0]=0;
	q.push(0);
	memset(num,0,sizeof(num));
	memset(vis,0,sizeof(vis));
	vis[0]=1; 
	num[0]=1;
	while(!q.empty()){
		int op=q.front();
		q.pop();
		vis[op]=0;
		for(int i=head[op];i!=-1;i=ed[i].next){
			int tt=ed[i].to;
			if(dis[tt]<dis[op]+ed[i].len){
				dis[tt]=dis[op]+ed[i].len;
				if(!vis[tt]){
					vis[tt]=1;
					num[tt]++;
					if(num[tt]>24) return -1;
					q.push(tt);
				}
				
			}
		}
	}
	if(dis[24]==x) return 1;
	else return 0;
}
int main(){
	int T,x;
	scanf("%d",&T);
	while(T--){
		memset(t,0,sizeof(t));
		memset(r,0,sizeof(r));
		for(int i=1;i<=24;i++){
			scanf("%d",&r[i]);  //输入需要的人数 
		}
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			scanf("%d",&x);
			t[x+1]++; //应征的人数 
		}
		int i;
		for(i=0;i<=n;i++){
			ju(i);
			if(spfa(i)>0){
				printf("%d
",i);
				break;
			}
		}
		if(i==n+1) {
			printf("No Solution
");
		}
	}
return 0;
}

1511:【SCOI2011】糖果

 

 这道题算是模板题了,记得是小的连向大的

然后还有一些判断条件

建立一个超级源点,但是这道题对spfa卡时间,所以倒着连(why!!!

因为这道题卡时间,卡SPFA,所以倒着连边(0-->1~n)

建立超级源点是因为可能不是连通图,而且因为每个小朋友都要分到糖果,所以超级源点连接权值为1

而且图里面可能有正环????(这个咋知道的,所以在SPFA里面要判断入队次数(因为是最长路

顺嘴一提:相等是连边add(x,y,0),add(y,x,0)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+5;
int n,k,h[N],net[N],to[N],cnt,w[N],dis[N],tot[N];
bool vis[N];
queue<int>q;
inline void add(int u,int v,int c) {
    to[++cnt]=v;net[cnt]=h[u];h[u]=cnt;w[cnt]=c;
}
int main(){
    scanf("%d%d",&n,&k);
    int u,v,f;
    while (k--) {
        scanf("%d%d%d",&f,&u,&v);
        if (f==1) add(u,v,0),add(v,u,0);
        else 
           if(f==2) {
              if(u==v){puts("-1");return 0;}
              add(u,v,1);
           }
        else if(f==3) add(v,u,0);
        else if(f==4) {
            if(v==u){puts("-1");return 0;}
            add(v,u,1);
        }
        else if(f==5)add(u,v,0);
    }
    for(int i=n;i>=1;i--) add(0,i,1);
    vis[0]=1,q.push(0);
    while(!q.empty()){
        int u=q.front();q.pop();vis[u]=0;
        if(tot[u]==n-1) { puts("-1");return 0; }
        tot[u]++;
        for(int i=h[u];i;i=net[i])
            if(dis[to[i]]<dis[u]+w[i]){
                dis[to[i]]=dis[u]+w[i];
                if(!vis[to[i]]) vis[to[i]]=1,q.push(to[i]);
            }
    }
    ll ans=0;
    for (int i=1;i<=n;i++) ans+=dis[i];
    printf("%lld
",ans);
    return 0;
}

  再看一下别的题解,发现大家很多还缩了点,用tarjin,或者是用的并查集合并了起来,懒得看了

1512:排队布局

读题!!!真的读题啊啊啊啊

这道题相比上一个多了一个求出1号和n号之间可以相隔任意长度的判断
//对于约束条件,转化为di-dj<=x;求dn-d1最大值;
//于是对于di-dj<=x,由j向i连x的边,跑最短路。
//如果存在负环,输出-1;(负环:因为是求最短路

根据题目:(which i miss 

牛的编号是从小到大,牛可以在同一个位置呆着

然后建边:最多相隔-----b-a<=k  建边为a-->b  k

        至少相隔----b-a>=k --->  a-b<=-k  建边  b--->a  -k

然后是牛可以在同一个位置呆着add(i+1,i,0);  因为(i+1)-(i)>=0 所以 i-(i+1)<=0

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int maxm=20050;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//这道题相比上一个多了一个求出1号和n号之间可以相隔任意长度的判断 
//对于约束条件,转化为di-dj<=x;求dn-d1最大值; 
//于是对于di-dj<=x,由j向i连x的边,跑最短路。 
//如果存在负环,输出-1;


//连边:一定要读题目!!!!!!!!!!!!!!!!!!1 
int n,ml,md,tot;
int head[maxn],to[maxm],wei[maxm],nex[maxm],num[maxn];
LL dis[maxn];
int vis[maxn]; 
queue<int> q;
void add(int u,int v,int c){
	to[++tot]=v;
	wei[tot]=c;
	nex[tot]=head[u];
	head[u]=tot;
}
int spfa(){
	//跑最短路
	fill(dis,dis+maxn,INF);
	dis[1]=0;
	vis[1]=1;
	q.push(1);
	num[1]++;
	while(!q.empty()){
		int top=q.front();
		q.pop();
		vis[top]=0;
		for(int i=head[top];i;i=nex[i]){
			int tt=to[i];
			if(dis[tt]>dis[top]+wei[i]){
				dis[tt]=dis[top]+wei[i];
				if(!vis[tt]){
					vis[tt]=1;
					q.push(tt);
					num[tt]++;if(num[tt]>n) return -1;
				}
			}
		}
	} 
	if(dis[n]!=INF) return dis[n];
	else return -2;
}
int main(){
	scanf("%d %d %d",&n,&ml,&md);
	int k,a,b;
	//aaaaaaa你要读题呀!!编号是站的顺序呀!!!!所以如果顺序不对要交换 
	for(int i=0;i<ml;i++){
		scanf("%d %d %d",&a,&b,&k); //最多相隔k b-a<=k 
		if(a>b) swap(a,b);
		add(a,b,k);
	}
	for(int i=0;i<md;i++){
		scanf("%d %d %d",&a,&b,&k); //最少相隔k  b-a>=k --> a-b<=-k
 		if(a>b) swap(a,b);
		add(b,a,-k); 
	}
	//处理这句话:
	//所以可能有两头或者更多奶牛站在同一位置上。即使说,如果我们想象奶牛是站在一条数轴上的话,允许有两头或更多奶牛拥有相同的横坐标。
	//相邻的要保证顺序
	for(int i=1;i<n;i++) add(i+1,i,0);
	
	printf("%d
",spfa()); 
return 0;
}

  后面这两道题,都有建立超级源点的作用,在这样题目里面,通过建模得到的边,可以通过控制权重建立超级源点,就解决了一些不连通的问题

1595 -- 【差分约束系统】工程规划1252

造一栋大楼是一项艰巨的工程,它是有n个子任务构成的,给它们分别编号1,2,3,....,n(5<=n<=1000).由于对一些任务的起始条件有着严格地限制,所以每个任务的起始时间T1,T2,T3....,Tn并不是很容易确定的(但这些起始时间都是非负整数,因为它们必须在整个工程开始后启动).例如:挖掘完成后,紧接着就要打地基;但是混泥土浇筑完成后,却要等待一段时间再去掉模板.
  这种要求就可以用m(5<=m<=5000)个不等式表示,不等式形如ti-tj<=B代表i和j的起始时间必须满足的条件.每个不等式的右边都是一个常数B,这些常数可能不相同,但是它们都在区间(-100,100)内.
  你的任务就是写一个程序,当给定像上面那样的不等式后,找出一种可能的起始时间序列T1,T2,T3....,Tn,或者判断问题无解.对于有解的情况,要使最早进行的哪个任务和整个工程的起始时间,也就是说,T1,T2,T3....,Tn中至少有一个0.

Input

  第一行是用空格分开的两个正整数N和M,下面的M行每行有三个用空格分开的整数i,j,B对应着不等式ti-tj<=B.

Output

  如果有可行的方案,那么输出N行,每行都有一个非负整数且至少有一个0,按照顺序表示每个任务的起始时间.如果没有可行的方案,就输出信息NO SOLUTION.
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=5010;
const int maxm=5010;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
int n,m,tot;
int dis[maxn],vis[maxn];
int head[maxm],to[maxm],wei[maxm],nex[maxm];
int num[maxn];
void adde(int x,int y,int w){
	to[++tot]=y;
	nex[tot]=head[x];
	wei[tot]=w;
	head[x]=tot;
}
//我的问题就是:一定要先求出哪些是0开始的这种死脑筋---所以更简单的想法就忽略了---求出来,再减去最小的 
bool spfa(int x){
	queue<int> q;
	q.push(x);
	dis[x]=0;
	vis[x]=1;
	while(!q.empty()){
		int x=q.front();
		q.pop();
		vis[x]=0;
		++num[x];
		if(num[x]>=n) return false;
		for(int i=head[x];i;i=nex[i]){
			int t=to[i];
			if(dis[t]>dis[x]+wei[i]){
				dis[t]=dis[x]+wei[i];
				if(!vis[t]){
					q.push(t);vis[t]=1;
				}
			}
		}
	}
	return true;
}
int main(){
	scanf("%d %d",&n,&m);
	int x,y,z;
	for(int i=1;i<=m;i++){
		scanf("%d %d %d",&x,&y,&z);
		adde(y,x,z);
	}
	memset(dis,0x3f,sizeof(dis));
	//建立一个超级源点,避免用连通块
	for(int i=1;i<=n;i++) adde(n+1,i,0);
	if(spfa(n+1)==false){
		printf("NO SOLUTION");return 0;
	} 
	int kk=INF;
	for(int i=1;i<=n;i++){
		kk=min(kk,dis[i]);
	}
	for(int i=1;i<=n;i++){
		printf("%d
",dis[i]-kk);
	}
return 0;
}


//最后一个点错了????
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
inline int read()
{
    int ans = 0,op = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-') op = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        (ans *= 10) += ch - '0';
        ch = getchar();
    }
    return ans * op;
}
const int maxn = 2005;
struct egde
{
	int to,next,cost;
}e[maxn * 10];
int fir[maxn],alloc;
inline void adde(int u,int v,int w)
{
	e[++alloc].next = fir[u];
	fir[u] = alloc;
	e[alloc].to = v;
	e[alloc].cost = w;
}
int n,m;
int dis[maxn],popst[maxn],minm;
bool instack[maxn];
void spfa(int s)
{
	queue<int> q;
	memset(dis,0x3f,sizeof(dis));
	dis[s] = 0;
	q.push(s);
	instack[s] = 1;
	while(q.size())
	{
		int u = q.front();
		q.pop();
		popst[u]++;
		if(popst[u] > n - 1) { printf("NO SOLUTION"); return;}
		instack[u] = 0;
		for(int i = fir[u];i;i = e[i].next)
		{
			int v = e[i].to,w = e[i].cost;
			if(dis[v] > dis[u] + w)
			{
				dis[v] = dis[u] + w;
				if(!instack[v]) q.push(v),instack[v] = 1;
			}
		}
	}
	for(int i = 1;i <= n;i++) minm = min(minm,dis[i]);
	for(int i = 1;i <= n;i++) printf("%d
",dis[i] - minm);
}
int main()
{
	n = read(),m = read();
	for(int i = 1;i <= m;i++)
	{
		int u = read(),v = read(),w = read();
		adde(v,u,w);	
	}
	for(int i = 1;i <= n;i++) adde(n + 1,i,0);
	spfa(n + 1);
} 

  

1597 -- 【差分约束系统】01串(NOI99)1887

Description

  给定7个整数N,A0,B0,L0,A1,B1,L1,要求设计一个01串S=s1s2…si…sN,满足:
  1.si=0或si=1,1<=i<=N;
  2.对于S的任何连续的长度为L0的子串sjsj+1…sj+L0-1(1<=j<=N-L0+1),0的个数大于等于A0且小于等于B0;
  3.对于S的任何连续的长度为L1的子串sjsj+1…sj+L1-1(1<=j<=N-L1+1),1的个数大于等于A1且小于等于B1;
  例如,N=6,A0=1,B0=2,L0=3,A1=1,B1=1,L1=2,则存在一个满足上述所有条件的01串S=010101。

Input

  输入:仅一行,有7个整数,依次表示N,A0,B0,L0,A1,B1,L1(3<=N<=1000,1<= A0<=B0<=L0<=N,1<=A1<=B1<=L1<=N),相邻两个整数之间用一个空格分隔。

Output

  输出:仅一行,若不存在满足所有条件的01串,则输出一个整数-1,否则输出满足所有条件的01串中1的个数的最大值。
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=30010;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
int c[maxn];
int head[maxn];
struct node{
	int nex,to,wei;
}ed[maxn];
int tot;
int n,a0,b0,l0,a1,b1,l1;
int vis[maxn],num[maxn]; //还要考虑有没有环?是不是每一个c[]都是一个正常的值 
void adde(int x,int y,int w){
	ed[++tot].to=y;
	ed[tot].nex=head[x];
	ed[tot].wei=w;
	head[x]=tot;
}
int S,N;
bool spfa(){
	queue<int> q;
	memset(c,0x3f,sizeof(c));
	c[S]=0;
	q.push(S); //超级源点
	memset(vis,0,sizeof(vis)); 
	while(!q.empty()){
		int x=q.front();
		q.pop();
		vis[x]=0;
		++num[x];if(num[x]==n+1) return false;
		for(int i=head[x];i;i=ed[i].nex){
			int t=ed[i].to;
			if(c[t]>c[x]+ed[i].wei){
				c[t]=c[x]+ed[i].wei;
				if(!vis[t]){
					vis[t]=1;
					q.push(t);
				}
			}
		}
	}
	for(int i=1;i<=n;i++) if(c[i]==0x3f3f3f3f) return false;
	return true;
}
int main(){
	scanf("%d %d %d %d %d %d %d",&n,&a0,&b0,&l0,&a1,&b1,&l1);
	N=n+2;S=n+1;//建立一个超级源点 
	for(int i=l0;i<=n;i++){
		adde(i,i-l0,b0-l0);
		adde(i-l0,i,-a0+l0);
	}
	for(int i=l1;i<=n;i++){
		adde(i-l1,i,b1);
		adde(i,i-l1,-a1);
	}
	for(int i=0;i<n;i++){
		adde(i,i+1,1);
		adde(i+1,i,0);
	}
	adde(S,0,0); 
	
	if(spfa()) printf("%d",c[n]-c[0]);
	else printf("-1");
return 0;
}

  

原文地址:https://www.cnblogs.com/shirlybaby/p/12617251.html