区间---

区间


题目描述

image-20210710143855592


样例解释

  • 【1】给定区间[a,b] [ 3 , 7 ] [3,7] [3,7],那么 3 ≤ x ≤ 7 3\leq x\leq 7 3x7,所以 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 8x10,所以 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 6x8,所以 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 1x3,所以 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 10x11,所以 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 0ai,bi50000,但是呢,由于前缀和需要用到 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 1ai,bi50001

那么题目的意思也就是让我们从区间[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 SiSi11i50001,为什么呢?因为 S i S_i Si表示从区间 [ 1 , i ] [1,i] [1,i]中选出的 x x x的个数,而 S i − 1 S_{i-1} Si1表示从区间 [ 1 , i − 1 ] [1,i-1] [1,i1]中选出的 x x x的个数,因此 S i S_i Si必定是要 ≥ S i − 1 \geq S_{i-1} Si1的,由定义出发就可以知道
  • S i − S i − 1 ≤ 1 S_i-S_{i-1}\leq 1 SiSi11,为什么呢?因为 S i S_i Si表示从区间 [ 1 , i ] [1,i] [1,i]中选出的 x x x的个数,而 S i − 1 S_{i-1} Si1表示从区间 [ 1 , i − 1 ] [1,i-1] [1,i1]中选出的 x x x的个数,那么 S i − S i − 1 S_i-S_{i-1} SiSi1就表示第 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 SbSa1c

由于想要求的是变量 S [ 50001 ] S[50001] S[50001]的最小值,运用差分约束,那么就需要跑最长路。将上面三个限制条件重新整理一下:

  • S i ≥ S i − 1 + 0 S_i\geq S_{i-1}+0 SiSi1+0,那么就是从节点 S i − 1 S_{i-1} Si1向节点 S i S_i Si连一条权值为0的边
  • S i − 1 ≥ S i − 1 S_{i-1}\geq S_i-1 Si1Si1,那么就是从节点 S i S_{i} Si向节点 S i − 1 S_{i-1} Si1连一条权值为-1的边
  • S b ≥ S a − 1 + c S_b\geq S_{a-1}+c SbSa1+c,那么就是从节点 S a − 1 S_{a-1} Sa1向节点 S b S_b Sb连一条权值为 c c c的边

但是呢,我们还需要思考一下,是否满足差分约束的条件:从源点出发,是否一定可以走到所有的边,这是差分约束正确性的前提条件。根据第一个约束条件可知,从 S i − 1 S_{i-1} Si1节点可以走到节点 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} Si1 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卷心菜不卷Iris

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值