题目描述:
给定一个数轴上的
n
n
n 个区间,要求在数轴上选取最少的点使得第
i
i
i个区间
[
a
i
,
b
i
]
[a_{i}, b_{i}]
[ai,bi] 里至少有
c
i
c_{i}
ci 个点。
使用差分约束系统的解法解决这道题
input:
输入第一行一个整数 n n n 表示区间的个数,接下来的 n n n 行,每一行两个用空格隔开的整数 a , b a,b a,b 表示区间的左右端点。 1 < = n < = 50000 , 0 < = a i < = b i < = 50000 1 <= n <= 50000, 0 <= a_{i} <= b_{i} <= 50000 1<=n<=50000,0<=ai<=bi<=50000 并且 1 < = c i < = b i − a i + 1 1 <= c_{i} <= b_{i} - a_{i}+1 1<=ci<=bi−ai+1。
output:
输出一个整数表示最少选取的点的个数
样例输入:
5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1
样例输出:
6
解题思路:
该题目要求用差分约束系统来解决。在差分约束系统中,所有的不等式都是
x
i
−
x
j
≤
T
x_{i}-x_{j}\leq T
xi−xj≤T的形式,将其移项之后为
x
i
≤
T
+
x
j
x_{i}\leq T+x_{j}
xi≤T+xj,正好对应于图中求解最短路的松弛操作,并且松弛之后取得是等于号,也就是该不等式的上界,最大值,如果将不等式中的
≥
≥
≥换成
≤
≤
≤,那么我们就可以跑最长路,这样最后的结果取的是下界,最小值。
对于该题目,我们可以记 s u m [ i ] sum[i] sum[i]为数轴上 [ 0 , i ] [0,i] [0,i]之间选点的个数, s u m [ i ] sum[i] sum[i]就相当于最短路中的 d i s [ i ] dis[i] dis[i]。对于第 i i i个区间 [ a i , b i ] [a_{i},b_{i}] [ai,bi]要求至少有 c i c_{i} ci个点,那么就相当于 s u m [ b i ] − s u m [ a i − 1 ] ≥ c i sum[b_{i}]-sum[a_{i}-1]≥c_{i} sum[bi]−sum[ai−1]≥ci,并且题目要求我们求最小解,那么我们可以通过spfa跑一遍最长路,然后输出 s u m [ m a x ( b i ) ] sum[max(b_{i})] sum[max(bi)]。并且我们要保证 s u m sum sum是有意义的,所以我们要满足 0 ≤ s u m [ i ] − s u m [ i − 1 ] ≤ 1 0\leq sum[i]-sum[i-1]\leq 1 0≤sum[i]−sum[i−1]≤1即对于坐标上的 i i i号点我们有可能选也有可能不选。由于是求最小解,我们要将 ≤ ≤ ≤转变为 ≥ ≥ ≥,则上面那个式子就被拆分成两个,为 s u m [ i ] − s u m [ i − 1 ] ≥ 0 sum[i]-sum[i-1]≥0 sum[i]−sum[i−1]≥0 s u m [ i − 1 ] − s u m [ i ] ≥ − 1 sum[i-1]-sum[i]≥ -1 sum[i−1]−sum[i]≥−1,所以我们将图构建好之后,为了保证这个条件,我们还要在图里加两条边,一条是以 i − 1 i-1 i−1为起点, i i i为终点,权值为 0 0 0的边,另一条是以 i i i为起点, i − 1 i-1 i−1为终点,权值为 − 1 -1 −1的边。在图构建好之后,然后就可以通过spfa跑一边最长路,得到答案。
在实现方面,这一次我使用了链式前向星,考虑到stl中的vector可能会卡时间,所以就没再使用,在加边的时候,要向图中加入一条 ( a , b + 1 , c ) (a,b+1,c) (a,b+1,c)的边,因为要保证 s u m [ b i ] − s u m [ a i − 1 ] ≥ c i sum[b_{i}]-sum[a_{i}-1]≥c_{i} sum[bi]−sum[ai−1]≥ci中间有 b − a + 1 b-a+1 b−a+1个点,如果只是添加 ( a , b , c ) (a,b,c) (a,b,c),那么该区间内就只有 b − a b-a b−a个点,由于 a − 1 a-1 a−1可能会是负值,所以在添加边的时候没有让 a − 1 a-1 a−1,而是 b + 1 b+1 b+1。
代码:
#include<iostream>
#include<stdio.h>
#include<queue>
using namespace std;
struct edge{//边结构体,链式前向星
int st,ed,wei,next;
edge(){
}
};
int head[50010];
edge ed[150010];//边数组
int sum[50010];
int inc[50010];
int total,n;
void add(int u,int v,int w)
{//加边
ed[total].st=u;
ed[total].ed=v;
ed[total].wei=w;
ed[total].next=head[u];
head[u]=total;
total++;
}
int main()
{
total=0;
for(int i=0;i<50010;i++)
{//初始化
head[i]=-1;
sum[i]=-1000000;
inc[i]=0;
}
cin>>n;
int maxb=0;
for(int i=0;i<n;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b+1,c);//向图中加边
if(maxb<b+1)//始终将右端点的最大值保存在maxb
maxb=b+1;
}
for(int i=1;i<=maxb;i++)
{//为了保证sum有意义,加两条边
add(i-1,i,0);
add(i,i-1,-1);
}
sum[0]=0;//spfa
queue<int> qq;
qq.push(0);
inc[0]=1;
while(!qq.empty())
{
int temp=qq.front();
qq.pop();
inc[temp]=0;
for(int i=head[temp];i!=-1;i=ed[i].next)
{
int end=ed[i].ed;
if(sum[end]<sum[temp]+ed[i].wei)
{
sum[end]=sum[temp]+ed[i].wei;
if(inc[end]!=1)
{
qq.push(end);
inc[end]=1;
}
}
}
}
cout<<sum[maxb]<<endl;//输出sum[maxb]
}