CSP-差分约束系统及求解
知识简述
差分约束系统,是一种不等式系统,形式比较固定。具体的形式如下:
在上面的例子中我们可以看到,差分约束系统的核心约束即为m个形如:xi-xj<=ck的不等式,解即为使所有条件都成立的一组答案。值得一提的是,差分约束系统一般说来都有无穷多个解,因此我们往往需要固定一个初始值来保证可以求出唯一解。
对于差分约束系统一般有两种形式:
1、xi-xj<=ck,求解的上限–>即xi-xj=ck的情况
2、xi-xj>=ck,求解的下限–>即xi-xj=ck的情况
对这两种情况,可以使用相同的转换思路,将xj移到不等式右侧后可以得到xi<=ck+xj求上限和xi>=ck+xj求下限,熟悉最短路的松弛操作的同学就会发现,这个式子和松弛后得到的条件是一模一样的,也就是:将xi视为dis[i],将xj视为dis[j],ck视为边长,利用最短路算法来求解差分约束系统
进过转换后,具体的实现思路如下:
1、将每一个约束条件(例xi-xj<=ck)转换成一条有向边(j,i,ck),并存入图中
2、对生成好的图跑最短路算法(由于ck的值可能为负值,采用SPFA算法比较合理)
3、令dis[1]=0,得到的xi=dis[i]即为差分约束系统的一组解
两种特殊情况的处理:
1、出现负环,出现负环,说明某些点的dis[i]=-inf,则表达式变为xi-x1<=-Inf,我们无法找到这样的一个解,说明该差分约束系统无解。
2、出现某点不可达,说明某点满足dis[i]=inf,则表达式变为xi-x1<=Inf,该条件在任何取值下都满足,说明差分约束系统对xi无明确约束,可以取任何值。
题目概述
给定一个数轴上的 n 个区间,要求在数轴上选取最少的点使得第 i 个区间 [ai, bi] 里至少有 ci 个点
INPUT&输入样例
输入第一行一个整数 n 表示区间的个数,接下来的 n 行,每一行两个用空格隔开的整数 a,b 表示区间的左右端点。1 <= n <= 50000, 0 <= ai <= bi <= 50000 并且 1 <= ci <= bi - ai+1。
输入样例
5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1
OUTPUT&输出样例
输出一个整数表示最少选取的点的个数
输出样例:
6
题目重述
给定几个区间,每个区间内要求有n个点,求出能够满足条件的选择的最少点。
思路概述
由于题目要求最少点满足选点条件,看到这个题目的第一反应可能会想到是一道贪心算法的题目,且这道题和贪心问题里的区间选点问题十分相似。
但如果使用贪心算法,对多个点的贪心并不容易实现,可能需要比较复杂的模拟。这里介绍一种利用差分约束求解这道题的做法。
我们使用sum[i]来表示从源点0开始,选的点个数。(虽然题目中说明点的范围是1-5e4,但如果从sum[1]开始我们是无法衡量出1号点是否被选中,所以使用0号点开始操作比较方便)
条件A B a(A到B闭区间内选择a个点)可以转换为sum[B]-sum[A-1]>=a,通过类比发现该类型的条件与上述的差分约束系统十分相似,可以使用差分约束来巧妙求解。
但是只有这些边条件是不够的,因为实际意义,每个点只能是选择或者不选择两个可能,我们可以得出如下的条件0<=sum[i]-sum[i-1]<=1,对图中的每两点进行连接边即可。由于求的是最少点,所以需要将所有约束条件转换成xi-xj>=ck的形式。在转换完成后的图中跑一遍最长路即可得出解。
题目所求的最少点,也就是sum[maxi]的值。
题目源码(c++)
#include<iostream>
#include<stdio.h>
#include<queue>
using namespace std;
const int N=5e4+5;
struct Edge
{
int des;
int nxt;
int value;
}Edges[N];
int edge_cnt;
int point_cnt;
int head[N];
void init()
{
edge_cnt=0;
for(int i=0;i<=N;i++)
head[i]=-1;
}
void add(int x,int y,int value)
{
edge_cnt++;
Edges[edge_cnt].des=y;
Edges[edge_cnt].nxt=head[x];
Edges[edge_cnt].value=value;
head[x]=edge_cnt;
}
int vis[N];
int dis[N];
queue<int> q;
void SPFA()
{
while(!q.empty()) q.pop();
for(int i=0;i<=point_cnt;i++)
{
vis[i]=0;
dis[i]=0;
}
vis[0]=1,dis[0]=0;
q.push(0);
while(!q.empty())
{
int x=q.front();
q.pop();
vis[x]=0;
for(int i=head[x];i>=0;i=Edges[i].nxt)
{
int y=Edges[i].des;
if(dis[y]<dis[x]+Edges[i].value || (dis[y]==0&&dis[x]+Edges[i].value==0))
{
dis[y]=dis[x]+Edges[i].value;
if(vis[y]==0)
{
vis[y]=1;
q.push(y);
}
}
}
}
}
int main()
{
int limit_number;
cin>>limit_number;
int start,end,value;
init();
int max_number=0;
for(int i=0;i<limit_number;i++)
{
scanf("%d %d %d",&start,&end,&value);
add(start,end+1,value);
if(end+1>max_number) max_number=end+1;
}
point_cnt=max_number;
for(int i=1;i<=point_cnt;i++)
{
add(i,i-1,-1);
add(i-1,i,0);
}
SPFA();
cout<<dis[point_cnt];
return 0;
}