区间
题目描述
样例解释
- 【1】给定区间
[a,b]
为 [ 3 , 7 ] [3,7] [3,7],那么 3 ≤ x ≤ 7 3\leq x\leq 7 3≤x≤7,所以 x x x的可选范围为{3,4,5,6,7},其中要求选出来的 x x x的个数必须不少于 c = 3 c=3 c=3个 - 【2】给定区间
[a,b]
为 [ 8 , 10 ] [8,10] [8,10],那么 8 ≤ x ≤ 10 8\leq x\leq 10 8≤x≤10,所以 x x x的可选范围为{8,9,10},其中要求选出来的 x x x的个数必须不少于 c = 3 c=3 c=3个 - 【3】给定区间
[a,b]
为 [ 6 , 8 ] [6,8] [6,8],那么 6 ≤ x ≤ 8 6\leq x\leq 8 6≤x≤8,所以 x x x的可选范围为{6,7,8},其中要求选出来的 x x x的个数必须不少于 c = 1 c=1 c=1个 - 【4】给定区间
[a,b]
为 [ 1 , 3 ] [1,3] [1,3],那么 1 ≤ x ≤ 3 1\leq x\leq 3 1≤x≤3,所以 x x x的可选范围为{1,2,3},其中要求选出来的 x x x的个数必须不少于 c = 1 c=1 c=1个 - 【5】给定区间
[a,b]
为 [ 10 , 11 ] [10,11] [10,11],那么 10 ≤ x ≤ 11 10\leq x\leq 11 10≤x≤11,所以 x x x的可选范围为{10,11},其中要求选出来的 x x x的个数必须不少于 c = 1 c=1 c=1个
题目是意思是说想让我们从【1】、【2】、【3】、【4】、【5】中挑出 x x x,组成一个整数集合 Z Z Z,使得集合 Z Z Z中元素的个数最少。那么给我们的启发就是,从【1】到【5】中跳出的这些数 x x x应该尽可能的有交集,这样,才能使得选出最少的个数来组成集合 Z Z Z。
- 我们先看【1】和【4】,可以发现有交集元素3,由于【4】要求至少 c = 1 c=1 c=1,因此从【4】中我们挑出 x = 3 x=3 x=3就行了;
- 再来看【1】和【3】,可以发现有交集元素6,7,由于【1】中要求至少 c = 3 c=3 c=3,因此从【1】中挑选出 x = 3 , x = 7 , x = 6 , x = 7 x=3,x=7,x=6,x=7 x=3,x=7,x=6,x=7
- 再来看【2】和【3】,可以发现有交集元素8,由于【3】要求至少选出 c = 1 c=1 c=1,因此从【3】中我们挑出 x = 8 x=8 x=8
- 再来看【2】和【5】,有交集元素10,由于【5】要求至少选出 c = 1 c=1 c=1,因此从【5】中我们挑出 x = 10 x=10 x=10,由于【2】要求选出来的 x x x的个数必须不少于 c = 3 c=3 c=3个,因此从【2】中挑出 x = 8 , 9 , 10 x=8,9,10 x=8,9,10
- 综上,我们选出来的
x
x
x有
[3,6,7,8,9,10]
,也就是最少选出6个数,就可以构成一个整数集合 Z Z Z,此时可以满足题目给出的五个限制条件
核心思路
这题我们需要用到差分约束来求解,同时还需要用到前缀和(这点比较难想)
我们设S[i]
来表示从区间
[
1
,
i
]
[1,i]
[1,i]中选出的
x
x
x的个数。这里
0
≤
a
i
,
b
i
≤
50000
0\leq a_i,b_i\leq 50000
0≤ai,bi≤50000,但是呢,由于前缀和需要用到
S
[
0
]
S[0]
S[0],由定义可知
S
[
0
]
=
0
S[0]=0
S[0]=0,那么我们可以让
a
i
,
b
i
a_i,b_i
ai,bi都+1,即向右平移一个单位,空出0这个位置来表达前缀和
S
[
0
]
S[0]
S[0],平移之和并不影响最终结果。因此,此时
1
≤
a
i
,
b
i
≤
50001
1\leq a_i,b_i\leq 50001
1≤ai,bi≤50001。
那么题目的意思也就是让我们从区间[1,50001]
从选出最少的
x
x
x的个数,来构成整数
Z
Z
Z的集合。这道题肯定是会有解的,因为最坏情况下,我们把区间
[
1
,
50001
]
[1,50001]
[1,50001]中的所有数都选择了,那么此时集合
Z
Z
Z就有50001个元素,因此一定是有解的。那么这个解该怎么表示呢?由于我们不知道具体要选出多少个数,但是我们知道范围上限是50001,也就是
S
[
50001
]
S[50001]
S[50001]表示的是从区间
[
1
,
50001
]
[1,50001]
[1,50001]中选出的
x
x
x的最少的个数。因此,我们真正要求解的就是
S
[
50001
]
m
i
n
S[50001]_{min}
S[50001]min
那么这题该怎么用差分约束呢?我们需要根据思路和题目描述自己来找出差分约束的条件:
- S i ≥ S i − 1 , 1 ≤ i ≤ 50001 S_i\geq S_{i-1},1\leq i\leq 50001 Si≥Si−1,1≤i≤50001,为什么呢?因为 S i S_i Si表示从区间 [ 1 , i ] [1,i] [1,i]中选出的 x x x的个数,而 S i − 1 S_{i-1} Si−1表示从区间 [ 1 , i − 1 ] [1,i-1] [1,i−1]中选出的 x x x的个数,因此 S i S_i Si必定是要 ≥ S i − 1 \geq S_{i-1} ≥Si−1的,由定义出发就可以知道
- S i − S i − 1 ≤ 1 S_i-S_{i-1}\leq 1 Si−Si−1≤1,为什么呢?因为 S i S_i Si表示从区间 [ 1 , i ] [1,i] [1,i]中选出的 x x x的个数,而 S i − 1 S_{i-1} Si−1表示从区间 [ 1 , i − 1 ] [1,i-1] [1,i−1]中选出的 x x x的个数,那么 S i − S i − 1 S_i-S_{i-1} Si−Si−1就表示第 i i i个数,即整数 i i i它被选的个数,由于整数 i i i要么没有,如果有的话则最多只会有一个,因此由含义就可以知道选出来的第 i i i个数,它被选的个数最多为1
- 由题目描述”区间 [ a , b ] [a,b] [a,b]中最少要有 c c c个数,可推知: S b − S a − 1 ≥ c S_b-S_{a-1}\geq c Sb−Sa−1≥c
由于想要求的是变量 S [ 50001 ] S[50001] S[50001]的最小值,运用差分约束,那么就需要跑最长路。将上面三个限制条件重新整理一下:
- S i ≥ S i − 1 + 0 S_i\geq S_{i-1}+0 Si≥Si−1+0,那么就是从节点 S i − 1 S_{i-1} Si−1向节点 S i S_i Si连一条权值为0的边
- S i − 1 ≥ S i − 1 S_{i-1}\geq S_i-1 Si−1≥Si−1,那么就是从节点 S i S_{i} Si向节点 S i − 1 S_{i-1} Si−1连一条权值为-1的边
- S b ≥ S a − 1 + c S_b\geq S_{a-1}+c Sb≥Sa−1+c,那么就是从节点 S a − 1 S_{a-1} Sa−1向节点 S b S_b Sb连一条权值为 c c c的边
但是呢,我们还需要思考一下,是否满足差分约束的条件:从源点出发,是否一定可以走到所有的边,这是差分约束正确性的前提条件。根据第一个约束条件可知,从 S i − 1 S_{i-1} Si−1节点可以走到节点 S i S_i Si,因此可以从 S 0 S_0 S0走到 S 1 S_1 S1, S 1 S_1 S1走到 S 2 S_2 S2, ⋯ \cdots ⋯,从 S 50000 S_{50000} S50000走到 S 50001 S_{50001} S50001,因此,从源点出发,是可以走到所有的边的
问题:为什么这里的边数要开3倍呢?
由第一、二个约束条件可知, S i − 1 S_{i-1} Si−1和 S i S_{i} Si之间都有边,即双向边,由第三个约束条件可知,则还会连出一条边,因此会有3条边。
代码
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=50010,M=150010;
int n;
int h[N],e[M],ne[M],w[M],idx;
//dist[i]表示从起点到节点i的最长距离
int dist[N];
//循环队列
int q[N];
//判断一个节点是否已经入队了
bool st[N];
void add(int a,int b,int c)
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
//这里一定有解,因此并不需要判断是否存在负环了
void spfa()
{
//求最长路,则初始化为负无穷
memset(dist,-0x3f,sizeof dist);
int hh=0,tt=1;
dist[0]=0;
q[0]=0;
st[0]=true;
while(hh!=tt)
{
//取出队头元素
int t=q[hh++];
//循环队列满了
if(hh==N)
hh=0;
//节点t出队
st[t]=false;
//遍历节点t的所有邻接点
for(int i=h[t];~i;i=ne[i])
{
int j=e[i];//t的邻接点编号j
if(dist[j]<dist[t]+w[i])
{
//通过节点t来更新节点j到起到的最长距离
dist[j]=dist[t]+w[i];
//如果节点j还没有入队
if(!st[j])
{
q[tt++]=j;//节点j入队
//队尾指针走到了末尾,则重新回到队头
if(tt==N)
tt=0;
//标记节点j已经入队了
st[j]=true;
}
}
}
}
}
int main()
{
//初始化表头为-1
memset(h,-1,sizeof h);
scanf("%d",&n);
for(int i=1;i<N;i++)
{
//给第一、二个约束条件建图
add(i-1,i,0);
add(i,i-1,-1);
}
while(n--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
//记得向右平移一位
a++,b++;
add(a-1,b,c); //给第三个约束条件建图
}
//跑以便spfa求出每个节点到起点的最长路
spfa();
//dist[50001]表示节点S(50001)到起点的最长距离,也就是题目中想要求的最小值
printf("%d\n",dist[50001]);
return 0;
}