LCP 32. 批量处理任务

传送门

https://leetcode-cn.com/problems/t3fKg1/

题目大意

给出n条线段 ( l , r , n e d ) (l,r,ned) (l,r,ned),表示从 l l l r r r,需要包含 n e d ned ned个点,要求你在数轴上选最少的整数点(一个点只能选一次),使得所有线段的需求都被满足。保证有解。
l , r ≤ 1 0 9 , n ≤ 1 0 5 l,r\leq 10^9, n\leq 10^5 l,r109,n105

题解

Step 1:和经典的贪心问题比较类似,很容易想出一个贪心做法,先把线段按右端点排序,然后依次处理线段的需求,如果已经选了足够的点,就不操作,否则尽量选靠右的点。因为这样能在满足当前线段需求的前提下,最大化满足后面线段的需求,显然这个策略是正确的。

Step 2:直接模拟,复杂度 O ( n ∗ max ⁡ ( r ) ) O(n*\max(r)) O(nmax(r))爆炸无疑,容易看出可以加上离散化,优化到 O ( n 2 ) O(n^2) O(n2)

Step 3:“尽量选靠右的点”,在朴素做法中需要枚举才能找到可选择的点(注意,为什么不是直接选靠右的若干个点?因为这些点之前也许被选过),在这里花费了 O ( n ) O(n) O(n)的时间,如何优化?用一个链表即可,使用 n e x t ( i ) next(i) next(i)表示 i i i左边第一个没有被选择的位置。这样能 O ( 1 ) O(1) O(1)找到可用的点(比如,第一个可用点是 n e x t ( r ) next(r) next(r))。考虑维护它,当一个点被选择以后,将其在链表里删除即可,方法很多。不过需要注意,坐标之前经过了离散化,所以一个节点代表了之前的一段区间,只有当该区间的点全部被选择之后才能将该节点删除(具体如何操作?我们并不关心哪些点被选了,直接用一个变量记录该区间被选择了多少或者剩下多少即可)这部分总复杂度变为 O ( n ) O(n) O(n)
唯一剩下的问题是如何计算“是否满足当前线段需求”,是一个简单的区间和问题,使用具有单点加和区间求和的数据结构处理即可,我选择了树状数组,因为好写,常数小。这部分复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

题外话

我在场上只想到step 2.5,然后就在线段树上去做了,当时想的是通过不断往下分裂的方式凑够需要的点数,即只做一次update操作,而不是不断选点不断update,没写完。后来想一想,线段树的时间复杂度是对的,只不过就是非常难写,因为这题本身带了离散化,就很容易出错,况且常数也更大,当时着急了,怕写不完,没仔细想。
这个题算是组装题,大概三部分,离散化+贪心+链表,链表这个技巧最初应该是在某个染色的题目上遇见的,难度不算很大,场上调T4调太久了,没剩多少时间,虽然补题我也花了很多时间hhh

代码

class Solution {
    #define maxn 100010
    class Task{
    public:
        int l,r,ned;
        Task(int l=0,int r=0,int ned=0):l(l),r(r),ned(ned){}
        bool operator < (const Task &rhs)const{
            return r<rhs.r;
        }
    }t[maxn];
    int x[maxn*2];
    int filled[maxn*2];//filled[i]表示第i块已经填充了多少

    //某个位置大小
    int size(int pos){return x[pos+1]-x[pos];}

    int nxt[maxn*2];

    //树状数组用于求和
    class Bit
    {
        int C[maxn*2];
        int n;

    public:
        Bit(int _n):n(_n){memset(C,0,sizeof C);}
        #define lowbit(x)  ((x)&(-(x)))
        void update(int pos,int v){
            for(int i=pos;i<=n;i+=lowbit(i)) C[i]+=v;
        }
        int query(int pos){
            int ret=0;
            for(int i=pos;i;i-=lowbit(i)) ret+=C[i];
            return ret;
        }
        int query(int l,int r){return query(r)-query(l-1);}
    };

public:
    Solution(){
        memset(x,0,sizeof x);
        memset(filled,0,sizeof filled);
        memset(nxt,0,sizeof nxt);
    }
    int processTasks(vector<vector<int>>& tasks) {
        int n=tasks.size();
        for(int i=0;i<n;i++){
            t[i+1]=Task(tasks[i][0],tasks[i][1]+1,tasks[i][2]);
            x[i*2+1]=t[i+1].l;
            x[i*2+2]=t[i+1].r;
        }
        //离散化
        sort(x+1,x+n*2+1);
        int xN=unique(x+1,x+n*2+1)-x-1;
        for(int i=1;i<=n;++i){
            t[i].l=lower_bound(x+1,x+xN+1,t[i].l)-x;
            t[i].r=lower_bound(x+1,x+xN+1,t[i].r)-x;
        }

        //链表
        for(int i=1;i<=xN+1;++i) nxt[i]=i-1;

        Bit bit(xN);
        sort(t+1,t+n+1);
        for(int i=1;i<=n;++i)
        {
            //已经开机的时间
            int got=bit.query(t[i].l,t[i].r-1);
            if(got<t[i].ned)
            {
                //还需要补足的时间
                int ned = t[i].ned-got;
                for(int j=nxt[t[i].r];;j=nxt[j])
                {
                    int left = size(j)-filled[j];//还可以用于填充的部分
                    //整个填充
                    if(left<ned){
                        bit.update(j,left);
                        //填充满,标记上,下次使用下一个块
                        filled[j]+=left;
                        nxt[t[i].r]=nxt[j];
                        ned-=left;
                    }
                    //部分填充 left>=ned
                    else{
                        bit.update(j,ned);
                        filled[j]+=ned;
                        //该块填充满
                        if(filled[j]==size(j)) nxt[t[i].r]=nxt[j];
                        break;
                    }
                }
            }
        }
        return bit.query(xN);
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值