区间选点II(差分约束 附spfa优化)

问题描述

给定一个数轴上的 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]) xjxi<=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 x1x01
  • x 2 − x 0 ≤ 2 x_2-x_0\le 2 x2x02
  • x 3 − x 0 ≤ 4 x_3-x_0\le 4 x3x04
  • x 2 − x 1 ≤ 3 x_2-x_1\le 3 x2x13
  • x 3 − x 2 ≤ 1 x_3-x_2\le 1 x3x21

在这个例子中,根据第3条我们可以知道 x 3 − x 0 ≤ 4 x_3-x_0\le 4 x3x04
根据第1、4、5条我们可以知道 x 3 − x 0 ≤ 5 x_3-x_0\le 5 x3x05
而根据第2、5条可以知道 x 3 − x 0 ≤ 3 x_3-x_0\le 3 x3x03
那么此时如果规定 x 0 = 0 x_0=0 x0=0,则 x 3 x_3 x3的最大值就是3

我们得出一个结论,求解 x i − x j x_i-x_j xixj的最大值,其实就是求解诸多不等式中 b k b_k bk值最小的一个,只要满足了最小的 b b b,那么其他的所有都会满足,那么求解差分约束系统,都可以转化为图论中单源最短路问题。(如果是 ≥ \ge 则可以求解最长路)

对于差分约束中的每一个不等式约束 x i − x j ≤ b k x_i-x_j\le b_k xixjbk都可以移项变形为 x i ≤ b k + x j x_i\le b_k+x_j xibk+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 xixjbk,当作从结点 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,这两种优化方案直接看这个大佬写的博客吧。

这里列一下所有不等式可能的情况:

  1. x i − x 1 ≥ T x_i-x_1\ge T xix1T,可以移项转化为 x 1 − x i ≤ − T x_1-x_i\le -T x1xiT(当然,也可以跑最长路)
  2. x i − x 1 < T x_i-x_1<T xix1<T,在整数域上可以转化为 x i − x 1 ≤ T − 1 x_i-x_1\le T-1 xix1T1
  3. x i − x 1 = T x_i-x_1=T xix1=T,可以转化为 x i − x 1 < = T x_i-x_1<=T xix1<=T x 1 − x i ≥ T x_1-x_i\ge T x1xiT

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[ai1]ci
  • 同时我们还要保证sum是有意义的: 0 ≤ s u m [ i ] − s u m [ i − 1 ] ≤ 1 0\le sum[i]-sum[i-1]\le 1 0sum[i]sum[i1]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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值