关于差分约束系统问题

其实差分系统如何解我们都知道,建个图,跑最短路或者最长路就行了。问题就在于我怎么知道这个就是用这种方法解的?(难受)
我觉得难就难在我们怎么从已知条件推出一个不等式d[v]<=d[u]+w(跑最短路)[u->v=w];或者d[v]+w>=d[u] (跑最长路)[u->v=w];

那么不等式是怎么转换为图的?以及到底要算最长路还是最短路呢?

1、不等式是怎么转换为图的?

答:先找出形式如a-b<=w不等式,这种不等式可以变成a<=b+w
我们知道,对于最短路,有这样的不等式:dis(u) ≤ dis(v) + len(v,u) (v,u是由一条长度为len(v,u)的有向边连接的两个点,dis(v)与dis(u)表示起点到达v或u的最短路)。
变形得:dis(u) - dis(v) ≤ len(v,u),与a-b ≤ c是不是非常相似?
所以,对于形如a-b ≤ c的不等式,我们可以从点b向点a连接一条长度为c的边。这样,我们就可以把不等式转换为图。

2、构造出图之后,到底是算最短路还是最长路呢?

答:若题目中说要求最小的话,那么我们要跑最长路;若求最大的,那么我们要跑最短路。

3、有哪些限定关系可以构造不等式呢?

答:a比b至多多c:a-b ≤ c;
a比b至少多c:a-b≥c;
a与b相等:a-b ≤ 0 且 a-b ≥ 0;
a比b恰好多c:a-b ≤ c 且 a-b ≥ c.

4、什么情况是无解呢?

答:只要有负权回路就是无解,并且可能存在负权边,所以一般用SPFA。

还不是很懂?那现在通过一题例题来看看,这题我刚开始开始时也没想到是用差分约束系统解决的:

跑最短路:

Photo(别怕,有翻译的)

Description
Farmer John has decided to assemble a panoramic photo of a lineup of his N cows (1 ≤ N ≤ 200,000), which, as always, are conveniently numbered from 1…N. Accordingly, he snapped M (1 ≤ M ≤ 100,000) photos, each covering a contiguous range of cows: photo i contains cows a_i through b_i inclusive. The photos collectively may not necessarily cover every single cow.

After taking his photos, FJ notices a very interesting phenomenon: each photo he took contains exactly one cow with spots! FJ was aware that he had some number of spotted cows in his herd, but he had never actually counted them. Based on his photos, please determine the maximum possible number of spotted cows that could exist in his herd. Output -1 if there is no possible assignment of spots to cows consistent with FJ’s photographic results.

Input

  • Line 1: Two integers N and M.

  • Lines 2…M+1: Line i+1 contains a_i and b_i.

Output

  • Line 1: The maximum possible number of spotted cows on FJ’s farm, or -1 if there is no possible solution.

Sample Input
5 3
1 4
2 5
3 4

Sample Output
1

Hint
INPUT DETAILS:

There are 5 cows and 3 photos. The first photo contains cows 1 through 4, etc.

OUTPUT DETAILS:

From the last photo, we know that either cow 3 or cow 4 must be spotted. By choosing either of these, we satisfy the first two photos as well.

Source
USACO 2013 Open
洛谷P3084
农夫约翰决定给站在一条线上的N(1 <= N <= 200,000)头奶牛制作一张全家福照片,N头奶牛编号1到N。

于是约翰拍摄了M(1 <= M <= 100,000)张照片,每张照片都覆盖了连续一段奶牛:第i张照片中包含了编号a_i 到 b_i的奶牛。但是这些照片不一定把每一只奶牛都拍了进去。

在拍完照片后,约翰发现了一个有趣的事情:每张照片中都有且仅有一只身上带有斑点的奶牛。约翰意识到他的牛群中有一些斑点奶牛,但他从来没有统计过它们的数量。 根据照片,请你帮约翰估算在他的牛群中最多可能有多少只斑点奶牛。如果无解,输出“-1”。
输入输出格式
输入格式:

  • Line 1: Two integers N and M.

  • Lines 2…M+1: Line i+1 contains a_i and b_i.

输出格式:

  • Line 1: The maximum possible number of spotted cows on FJ’s farm, or -1 if there is no possible solution.

输入样例

5 3 
1 4 
2 5 
3 4 

输出

1

那么不等式怎么出来呢?
设D[i]为前i头奶牛中斑点奶牛的数目,我们要求的就是max(D[n])
1.看题目“站在一条线上的N(1 <= N <= 200,000)头奶牛制作一张全家福照片,N头奶牛编号1到N。”,那么显然有:d[i-1]<=d[i],这个不等式我们看成d[i-1]<=d[i]+0,我们建图时,在i~i-1之间建一条权值为0的边。并且每只奶牛要么是斑点奶牛,要么不是。则0<=D[i]−D[i−1]<=1 。
那么在i-1~i之间建一条权值为1的边。
2.看输入格式,输入a b
依照题意那么d[b]-d[a]=1,Why?因为ab之间是必定有一个斑点牛的,那么怎么搞出一个不等式呢?
因为d[b]-d[a]=1
那么同时满足:d[b]-d[a]>=1和d[b]-d[a]<=1
这样我们就可以得出两个不等式了,建图时我们看成
d[a]<=d[b]-1 和 d[b]<=d[a]+1 那么b~a之间建一条权值为-1的边,在 a ~b之间建一条权值为1的边(有向边哦),我觉得说到这里大家就都懂了吧?
什么?大概?那么就在看看洛谷的题解吧—https://fakesilhouette.blog.luogu.org/solution-p3084
代码在这,本题难就难在SPFA被卡了,网上大佬多还是用SPFA卡过去了,orz
我觉得思想重要。

#include<bits/stdc++.h>
using namespace std;
int inf=99999999;
int n,dis[200005],book[200005];
int countt[200005];
vector <int> e[200005],zhi[200005];//一个储存图,一个储存边的权值
int SPFA()//优化就是如果对一个点更新,最短路比当前队首还要小,就直接加在队首,要不就加在队尾 
{//SLF
	int u,v,w;
	deque<int>q;//双端队列
	memset(book,0,sizeof(book));
	memset(countt,0,sizeof(countt));
	fill(dis+1,dis+1+n,inf);
	dis[0]=0;//0开始
	q.push_front(0);//双端队列头部增加一个元素 0
	book[0]=1;
	countt[0]++;
	int flag=0;
	int ka=0; 
	while(!q.empty())
	{
		u=q.front();//返回队首元素 
		q.pop_front();//删除双端队列中最前一个元素
		for(int i=0;i<e[u].size();i++){
			v=e[u][i];
			w=zhi[u][i];
			if(dis[v]>dis[u]+w)
			{
				dis[v]=dis[u]+w;//使dis[v]<=dis[u]+w 不等式就是参照这里建图的
				if(book[v]==0)//不在队列中,加入 
				{
					book[v]=1;
					countt[v]++;
					if(countt[v]>n)// //如果某个顶点 入队超过n次则存在负权环
					{
						flag=1;
						return -1;//无解输出-1 
						break;
					}
					//还是超时!那就卡一下800000就好了 网上大佬给的 
					if(++ka>800000)
					return -1;
				    if(q.size()&&dis[v]<dis[q.front()])
					{
						q.push_front(v);//最短路比当前队首还要小,就直接加在队首
					}
					else
					q.push_back(v);//否则就加在队尾  
					 
		    	}
			}
		}
		book[u]=0;//取消队首标记 
	}
	return dis[n];
}
int main(void)
{
	int m,a,b;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d %d",&a,&b);
		e[a-1].push_back(b);
		zhi[a-1].push_back(1);//a~b之间建一条权值为1的边
		//a-1使区间左闭右开,牛总不能劈开吧 
		e[b].push_back(a-1);
		zhi[b].push_back(-1);//b~a之间建一条权值为-1的边
	}
	for(int i=1;i<=n;i++){
		e[i].push_back(i-1);
		zhi[i].push_back(0);//i~i-1之间建一条权值为0的边
		
		e[i-1].push_back(i); 
		zhi[i-1].push_back(1);//i-1~i之间建一条权值为1的边
	}//i-1可能为0,所以上面最短路要从0开始跑
	printf("%d\n",SPFA());
	return 0;
}

跑最长路

洛谷P1250

题目描述
一条街的一边有几座房子。因为环保原因居民想要在路边种些树。路边的地区被分割成块,并被编号成1…N。每个部分为一个单位尺寸大小并最多可种一棵树。每个居民想在门前种些树并指定了三个号码B,E,T。这三个数表示该居民想在B和E之间最少种T棵树。当然,B≤E,居民必须记住在指定区不能种多于区域地块数的树,所以T≤E-B+l。居民们想种树的各自区域可以交叉。你的任务是求出能满足所有要求的最少的树的数量。

写一个程序完成以下工作:

输入输出格式
输入格式:
第一行包含数据N,区域的个数(0<N≤30000);

第二行包含H,房子的数目(0<H≤5000);

下面的H行描述居民们的需要:B E T,0<B≤E≤30000,T≤E-B+1。

输出格式:
输出文件只有一行写有树的数目
输入样例#1:

9
4
1 4 2
4 6 2
8 9 2
3 5 2

输出样例#1:

5

分析:

两个点之间(相当于左边的一个点)只能种0~1棵树:那么有不等式:
0<=d[i]-d[i-1]<=1;该不等式可化为:

1.d[i-1]<=d[i] ,则在i-1~i之间建一条权值为0的边。
2.d[i]-1<=d[i-1], 则在i~i-1之间建一条权值为-1的边。

假设输入 a,b,c
那么依据题意有:d[b]-d[a]>=c;
可变为d[a]+c<=d[b], 则在a~b之间建一条权值为c的边;

然后跑一遍最长路就行了,SPFA用一下双端队列优化会快很多哦。

#include<bits/stdc++.h>
using namespace std;
int inf=-99999999;
int book[30005]; 
int dis[30005];//理解:这时的dis[j]表示前j家种了多少颗树,ans=dis[n]
int n;
vector<int> e[30005],zhi[30005];//个人比较喜欢这样用 
void SPFA(int s/*源点*/)
{
	int u,v,w;
	deque<int>q;//双端队列
	memset(book,0,sizeof(book));
	fill(dis,dis+n+1,inf);//初始化
	dis[s]=0; 
	q.push_front(s);book[s]=1;
	while(!q.empty())
	{
		u=q.front();//返回队首元素
		q.pop_front();//删除双端队列中最前一个元素
		for(int i=0;i<e[u].size();i++){
			v=e[u][i];
			w=zhi[u][i];
			if(dis[v]<dis[u]+w)求最长路,最短路才是dis[v]>dis[u]+w 
			{
				dis[v]=dis[u]+w;
				if(book[v]==0)//进队 
				{
					book[v]=1;
					if(q.size()&&dis[v]<dis[q.front()])
					{
						q.push_front(v);//比当前队首还要小,就直接加在队首
					}
					else
					q.push_back(v);//否则就加在队尾  
				}
			}
		}
		book[u]=0;
	}
}
int main(void)
{
	int h,a,b,c;
	scanf("%d",&n);
	scanf("%d",&h);
	for(int i=1;i<=h;i++){
		scanf("%d %d %d",&a,&b,&c);
		//a-1使区间左闭右开,因为都是整数 
		e[a-1].push_back(b);
		zhi[a-1].push_back(c);//a->b:c
	}
	for(int i=1;i<=n;i++){
		e[i].push_back(i-1);
		zhi[i].push_back(-1);//i->i-1:-1
	}
	for(int i=0;i<n;i++){
		e[i].push_back(i+1);
		zhi[i].push_back(0);//i-1->i:0
	}
	SPFA(0);
	//for(int i=0;i<=n;i++){
	//	printf("%d\n",dis[i]);
	//}
	printf("%d\n",dis[n]); 
	return 0;
}	//求最长路,也可以建负边然后最短路取负数

这题也可以用贪心来做;https://www.luogu.org/blog/41868/soutionp1250

想要种树种得少,就要一棵树在多个区间同时出现。
所以,在重叠部分种尽可能多的树即可
然而重叠部分一定在区间的尾部
所以先对结束苇子进行排序,然后依次在区间的尾部从前往后种树直到满足要求,对于下一个区间,看看差多少树,就在结尾补多少。

于是贪心的思想就很容易出来了:

1.按结束位置排序
2.对每个区间一次处理
3.从前往后扫描区间,统计已有的树的个数
4.若已选点超过要求个数,则continue
5.否则从后往前,添加缺少的覆盖点
3.输出ans
就是区间选点

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值