问题描述
给定一个数轴上的 n 个区间,要求在数轴上选取最少的点使得第 i 个区间 [ai, bi] 里至少有 ci 个点
Input
输入第一行一个整数 n 表示区间的个数,接下来的 n 行,每一行两个用空格隔开的整数 a,b 表示区间的左右端点。1 <= n <= 50000, 0 <= ai <= bi <= 50000 并且 1 <= ci <= bi - ai+1。
Output
输出一个整数表示最少选取的点的个数
Sample input
5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1
Sample output
6
解题思路
上一道区间选点的题是使用贪心来做的,这次学了差分约束,使用差分约束来解一下这道题。
先说一下什么是差分约束:
差分约束
所谓差分约束系统(system of difference constraints),是求解关于一组变数的特殊不等式组之方法。如果一个系统由n个变量和m个约束条件组成,其中每个约束条件形如 x j − x i < = b k ( i , j ∈ [ 1 , n ] , k ∈ [ 1 , m ] ) x_j-x_i<=b_k(i,j∈[1,n],k∈[1,m]) xj−xi<=bk(i,j∈[1,n],k∈[1,m]),其中 b k b_k bk是常数(可以是非负数,也可以是负数)
我们要解决的问题是:求一组解 x 1 = a 1 , x 2 = a 2 , . . . , x n = a n x_1=a_1,x_2=a_2,...,x_n=a_n x1=a1,x2=a2,...,xn=an,使得所有的约束条件得到满足,否则判断无解。通俗一点地说,差分约束系统就是一些不等式的组,而我们的目标是通过给定的约束不等式组求出最大值或者最小值或者差分约束系统是否有解。
考虑到一组式子:
- x 1 − x 0 ≤ 1 x_1-x_0\le 1 x1−x0≤1
- x 2 − x 0 ≤ 2 x_2-x_0\le 2 x2−x0≤2
- x 3 − x 0 ≤ 4 x_3-x_0\le 4 x3−x0≤4
- x 2 − x 1 ≤ 3 x_2-x_1\le 3 x2−x1≤3
- x 3 − x 2 ≤ 1 x_3-x_2\le 1 x3−x2≤1
在这个例子中,根据第3条我们可以知道
x
3
−
x
0
≤
4
x_3-x_0\le 4
x3−x0≤4
根据第1、4、5条我们可以知道
x
3
−
x
0
≤
5
x_3-x_0\le 5
x3−x0≤5
而根据第2、5条可以知道
x
3
−
x
0
≤
3
x_3-x_0\le 3
x3−x0≤3
那么此时如果规定
x
0
=
0
x_0=0
x0=0,则
x
3
x_3
x3的最大值就是3
我们得出一个结论,求解 x i − x j x_i-x_j xi−xj的最大值,其实就是求解诸多不等式中 b k b_k bk值最小的一个,只要满足了最小的 b b b,那么其他的所有都会满足,那么求解差分约束系统,都可以转化为图论中单源最短路问题。(如果是 ≥ \ge ≥则可以求解最长路)
对于差分约束中的每一个不等式约束 x i − x j ≤ b k x_i-x_j\le b_k xi−xj≤bk都可以移项变形为 x i ≤ b k + x j x_i\le b_k+x_j xi≤bk+xj。
如果我们令 b k = w ( i , j ) , d i s [ i ] = x i , d i s [ j ] = x j b_k=w(i,j),dis[i]=x_i,dis[j]=x_j bk=w(i,j),dis[i]=xi,dis[j]=xj,那么原式就可以变为 d i s [ i ] ≤ d i s [ j ] + w ( i , j ) dis[i]\le dis[j]+w(i,j) dis[i]≤dis[j]+w(i,j),这与最短路问题中的松弛操作相似:
if(dis[v]>dis[u]+w(u,v)){
dis[v]=dis[u]+w(u,v);
}
我们可以把变量 x i x_i xi看作图中的一个结点,对于每个不等式约束 x i − x j ≤ b k x_i-x_j\le b_k xi−xj≤bk,当作从结点 j j j到结点 i i i连接一条长度为 b k b_k bk到有向边。
于是求解差分约束问题变成了求解最短路问题。
如果最终令 x 1 = 0 x_1=0 x1=0,那么 x i = d i s [ i ] x_i=dis[i] xi=dis[i]便是差分约束问题的一组解。
我们再考虑一下什么时候无解
如果我们使用spfa求最长路,那么出现正环,系统无解;求最短路,出现负环,系统无解。
由于spfa有的时候经常会被卡,所以我们最好使用dfs优化的spfa,这种优化适用于判环。代码如下:
//visit[]表示被访问过了(即dis值有效了),binque[]表示正在访问
bool spfa(int x)//dfs优化的spfa
{
binque[x]=true;
for (int i=head[x]; i!=-1; i=table[i].next)
{
if(!visit[table[i].y] || dis[table[i].y]<dis[x]+table[i].w)
{
visit[table[i].y]=true;
dis[table[i].y]=dis[x]+table[i].w;
//上面两条语句不能在下面第二个之下,即if(!spfa...)
if(binque[table[i].y])//如果一个点通过一段路径后回到了自己且这段路径还比之前的优,意即存在所寻找的环,返回
return false;
if(!spfa(table[i].y))//存在一个正权环就够说明问题了,一路返回
return false;
}
}
binque[x]=false;
return true;
}
除了dfs优化,在无环的时候,另外两种优化:SLF和LLL,这两种优化方案直接看这个大佬写的博客吧。
这里列一下所有不等式可能的情况:
- x i − x 1 ≥ T x_i-x_1\ge T xi−x1≥T,可以移项转化为 x 1 − x i ≤ − T x_1-x_i\le -T x1−xi≤−T(当然,也可以跑最长路)
- x i − x 1 < T x_i-x_1<T xi−x1<T,在整数域上可以转化为 x i − x 1 ≤ T − 1 x_i-x_1\le T-1 xi−x1≤T−1
- x i − x 1 = T x_i-x_1=T xi−x1=T,可以转化为 x i − x 1 < = T x_i-x_1<=T xi−x1<=T 且 x 1 − x i ≥ T x_1-x_i\ge T x1−xi≥T
PS:值得一提的是如果你使用邻接表存图,推荐将边数组开大10倍,因为这里面总是会多出一些 ≥ 0 \ge 0 ≥0什么的辅助条件。
本题解法
下面我们看一下这个题的解法,首先要注意,求解差分约束系统的时候,先不要往图上联想,先把所有能列的不等式都列出来。
比如这个题我们可以构造不等式组
- 记 s u m [ i ] sum[i] sum[i]表示数轴上 [ 0 , i ] [0,i] [0,i]之间选点的个数
- 对于第 i i i个区间 [ a i , b i ] [a_i,b_i] [ai,bi],需要满足 s u m [ b i ] − s u m [ a i − 1 ] ≥ c i sum[b_i]-sum[a_i-1]\ge c_i sum[bi]−sum[ai−1]≥ci
- 同时我们还要保证sum是有意义的: 0 ≤ s u m [ i ] − s u m [ i − 1 ] ≤ 1 0\le sum[i]-sum[i-1]\le 1 0≤sum[i]−sum[i−1]≤1
这道题的所有不等式组都列出来了,我们将其转化为 ≥ \ge ≥不等式组跑最长路,答案就是 s u m [ m a x b i ] sum[max{b_i}] sum[maxbi]
由于这个题意隐含着不存在无解情况,所以千万别用dfs版的spfa,会TLE的死死的。
别问我怎么知道的🙃
这道题我用的SLF优化,不优化时间是579ms,优化后为547ms
完整代码
//#pragma GCC optimize(2)//比赛禁止使用!
//#pragma G++ optimize(2)
//#include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <climits>
#include <algorithm>
#include <queue>
#include <vector>
#include <deque>
using namespace std;
const int maxn=50000+10;
struct node
{
int to,next,w;
};
node edge[maxn*10];
int head[maxn],n,a,b,c,sum[maxn],cnt,mina,maxb;
bool vis[maxn],bin[maxn];
inline void add(int x,int y,int w)
{
cnt++;
edge[cnt].to=y;
edge[cnt].w=w;
edge[cnt].next=head[x];
head[x]=cnt;
}
bool DfsSpfa(int x)
{
bin[x]=true;
for (int i=head[x]; i; i=edge[i].next){
if(!vis[edge[i].to] || sum[edge[i].to]<sum[x]+edge[i].w){
vis[edge[i].to]=true;
sum[edge[i].to]=sum[x]+edge[i].w;
if(bin[edge[i].to]) return false;
if(!DfsSpfa(edge[i].to)) return false;
}
}
bin[x]=false;
return true;
}
void SlfSpfa(int s)
{
deque<int> q;
q.push_back(s);
sum[s]=0,vis[s]=true;
while(!q.empty()){
int u=q.front();
q.pop_front();
vis[u]=false;
for(int i=head[u]; i; i=edge[i].next){
int v=edge[i].to;
if(sum[v]<sum[u]+edge[i].w){
sum[v]=sum[u]+edge[i].w;
if(!vis[v]){
if(!q.empty() && sum[v]>=sum[q.front()]) q.push_back(v);//经过好多次提交,发现使用>=更优秀,但是不应该啊,按理说这是最长路,应该如果sum[v]大,放在队首更能提速?(欢迎评论区指点一下这个地方)
else q.push_front(v);
vis[v]=true;
}
}
}
}
}
inline void init()
{
for (int i=0; i<=maxb; i++)
sum[i]=INT_MIN/3;
}
int getint()
{
int x=0,s=1;
char ch=' ';
while(ch<'0' || ch>'9')
{
ch=getchar();
if(ch=='-') s=-1;
}
while(ch>='0' && ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return x*s;
}
int main()
{
n=getint();
for (int i=1; i<=n; i++){
a=getint(); b=getint(); c=getint();
add(a-1,b,c);
mina=min(mina,a);
maxb=max(maxb,b);
}
init();
for (int i=mina-1; i<maxb; i++){
add(i,i+1,0);
add(i+1,i,-1);
//add(maxb+1,i,0);
}
//add(maxb+1,maxb,0);
//DfsSpfa(maxb+1);//这个死了
SlfSpfa(mina-1);
cout<<sum[maxb]<<endl;
return 0;
}