每周题解:Intervals(区间)

题目链接

区间

题目描述

给定 n n n 个区间 [ a i , b i ] [a_i,b_i] [ai,bi] n n n 个整数 c i c_i ci

你需要构造一个整数集合 Z Z Z,使得 ∀ i ∈ [ 1 , n ] ∀i∈[1,n] i[1,n] Z Z Z 中满足 a i ≤ x ≤ b i a_i≤x≤b_i aixbi 的整数 x x x 不少于 c i c_i ci 个。

求这样的整数集合 Z Z Z 最少包含多少个数。

输入格式

第一行包含整数 n n n

输出格式

接下来 n n n 行,每行包含三个整数 a i , b i , c i a_i,b_i,c_i ai,bi,ci

样例 #1

样例输入 #1

5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1

样例输出 #1

6

提示

【数据范围】

1 ≤ n ≤ 50000 1≤n≤50000 1n50000,
0 ≤ a i , b i ≤ 50000 0≤a_i,b_i≤50000 0ai,bi50000,
0 ≤ c i ≤ b i − a i + 1 0≤c_i≤b_i−a_i+1 0cibiai+1

算法思想

根据题目描述,给定 n n n 个区间 [ a i , b i ] [a_i,b_i] [ai,bi],从每个区间至少 c i c_i ci 个数构造一个整数集合 Z Z Z,求集合 Z Z Z 最少包含多少个数?测试样例中有 5 5 5个区间:

  • [ 3 , 7 ] [3,7] [3,7]中至少选 3 3 3个数,可以选: 3 , 4 , 5 3,4,5 3,4,5
  • [ 8 , 10 ] [8,10] [8,10]中至少选 3 3 3个数,可以选: 8 , 9 , 10 8,9,10 8,9,10
  • [ 6 , 8 ] [6,8] [6,8]中至少选 1 1 1个数,可以选: 8 8 8
  • [ 1 , 3 ] [1,3] [1,3]中至少选 1 1 1个数,可以选: 3 3 3
  • [ 10 , 11 ] [10,11] [10,11]中至少选 1 1 1个数,可以选: 10 10 10

集合 Z Z Z 最少包含 6 6 6个数: 3 , 4 , 5 , 8 , 9 , 10 3,4,5,8,9,10 3,4,5,8,9,10

这里可以参考前缀和的思想,用 s [ i ] s[i] s[i]表示区间 [ 1 , i ] [1,i] [1,i]中选取数的个数,可以得到下面的一系列关系:

  • 从前 i i i个数中选取数的个数一定不少于从前 i − 1 i-1 i1个数中选, s [ i ] ≥ s [ i − 1 ] s[i]\ge s[i-1] s[i]s[i1]
  • 从前 i i i个数中取比从前 i − 1 i-1 i1个数中取、最多会多 1 1 1 , s [ i ] − s [ i − 1 ] ≤ 1 ⇒ s [ i − 1 ] ≥ s [ i ] − 1 ,s[i]-s[i-1]\le1\Rightarrow s[i-1]\ge s[i]-1 s[i]s[i1]1s[i1]s[i]1
  • 对于区间 [ a , b ] [a,b] [a,b]中至少取 c c c个数,那么 s [ b ] − s [ a − 1 ] ≥ c ⇒ s [ b ] ≥ s [ a − 1 ] + c s[b]-s[a-1]\ge c\Rightarrow s[b]\ge s[a-1]+c s[b]s[a1]cs[b]s[a1]+c

根据上述一系列不等式关系,可以用差分约束的思想来求集合 Z Z Z 中最少包含数的个数。差分约束可以参考博主的另一篇文章:每周算法:差分约束。

具体过程如下:

  • 对于 ∀ i ∈ [ 1 , n ] \forall i\in [1,n] i[1,n],存在不等式 s [ i ] ≥ s [ i − 1 ] s[i]\ge s[i-1] s[i]s[i1],从节点 i − 1 i-1 i1 i i i建一条有向边,边权为 0 0 0
  • 对于 ∀ i ∈ [ 1 , n ] \forall i\in [1,n] i[1,n],存在不等式 s [ i − 1 ] ≥ s [ i ] − 1 s[i-1]\ge s[i]-1 s[i1]s[i]1,从节点 i i i i − 1 i-1 i1建一条有向边,边权为 − 1 -1 1
  • 对于区间 [ a , b ] [a,b] [a,b]中至少取 c c c个数,存在不等式 s [ b ] ≥ s [ a − 1 ] + c s[b]\ge s[a-1]+c s[b]s[a1]+c,从节点 a − 1 a-1 a1 b b b建一条有向边,边权为 c c c
  • 建立一个虚拟源点 0 0 0 s [ 0 ] s[0] s[0]表示在前 0 0 0个数中选取 0 0 0个点。
  • 从虚拟源点 0 0 0出发求单源最长路。由于区间中每个点都有边相连,因此从虚拟源点 0 0 0出发可以访问到图中每个点,也就能访问每条边,满足差分约束系统中的不等式组。
  • 最终 s [ T ] s[T] s[T]就是集合 Z Z Z 中最少包含数的个数,这里 T T T指的是所有区间中最后一个数。

注意:

  • 由于 0 ≤ c i ≤ b i − a i + 1 0≤c_i≤b_i−a_i+1 0cibiai+1,说明最终问题一定有解,所以不用判断是否存在正环,最坏情况把区间中所有数都选上。
  • 由于 0 ≤ a i , b i ≤ 50000 0≤a_i,b_i≤50000 0ai,bi50000,区间开始位置包含 0 0 0点,而做题中需要建立一个 0 0 0作为虚拟源点,因此需要对所有区间右移一个数,避免取到 0 0 0点。

代码实现

#include <bits/stdc++.h>
using namespace std;
const int N = 50005, M = N * 3 + 5;
int h[N], e[M], w[M], ne[M], idx;
int n, T, s[N], q[N];
bool st[N];
void add(int a, int b, int c)  // 添加一条边a->b,边权为c
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

void spfa()
{
    memset(s, -0x3f, sizeof h);
    int hh = 0, tt = 0;
    q[tt ++] = 0, s[0] = 0, st[0] = true; //虚拟源点入队
    while(hh != tt)
    {
        int u = q[hh ++];
        if(hh == N) hh = 0;
        st[u] = false;
        for(int i = h[u]; ~ i; i = ne[i])
        {
            int v = e[i];
            if(s[v] < s[u] + w[i]) //求最长路
            {
                s[v] = s[u] + w[i];
                if(!st[v]) 
                {
                    q[tt ++] = v;
                    if(tt == N) tt = 0;
                    st[v] = true;
                }
            }
        }
    }
}

int main()
{
    scanf("%d", &n);
    memset(h, -1, sizeof h);
    for(int i = 1; i < N; i ++) //注意,这里要对区间中每个点建边
    {
        add(i - 1, i, 0); //s[i]>=s[i-1]
        add(i, i - 1, -1); //s[i-1]>=s[i]-1
    }
    for(int i = 1; i <= n; i ++)
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        a ++, b ++; //区间整体向右移动
        add(a -1, b, c); //s[b]>=s[a-1]+c
        T = max(T, b);
    }
    spfa(); //求差分约束
    printf("%d", s[T]); //输出前T个数中最少包含多少个数
    return 0;
}

  • 27
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

少儿编程乔老师

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

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

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

打赏作者

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

抵扣说明:

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

余额充值