LintCode 1329: Sequence maintenance (线段树和树状数组区间更新和区间查询,经典难题!!!)

1329. Sequence maintenance

There is a sequencea of length n, and there are q operations on the sequence.
Each operation has a queried number bi, subtracting one from all numbers that greater than or equal to bi in the sequence.
Returns how many numbers are subtracted by one after each operation.

Example

Example1

Input
4
3
[1,2,3,4]
[4,3,1]
Output
[1,2,4]

Example2

Input
3
2
[1,2,3]
[3,3]
Output
[1,0]

Notice

1≤n,q≤10000,1≤a​i​​,x≤n

Input test data (one parameter per line)How to understand a testcase?

解法1:用线段树的区间更新,利用延迟更新标志add[],每次区间更新时间复杂度为O(logn)。
下面讲线段树的区间更新的2个链接不错:
https://blog.youkuaiyun.com/luomingjun12315/article/details/51124526
https://blog.youkuaiyun.com/thinking2013/article/details/41621657

注意:
1) 为什么add[]和sum[]的size定为4*n?
因为我们实际上想实现一个满二叉树,最下面一层为n的话,一共有1,2,4,8,...,n个节点。
如果n是2^k的话,那么1+2+4+8+...+n 一共有k+1项,其sum= 1*(1-2^(k+1))/(1-2)=2 * 2^k = 2*n。
但实际上n不一定是2^k,最极端情况下n=2^(k+1) - 1,那样下面还有1层,所以取4*n。
下面这个链接讲得蛮好
https://www.cnblogs.com/silentEAG/p/10808978.html
2) 注意线段树也可以用数组实现,类似heap。实际上线段树完全可以替代heap,因为线段树的max, min操作都可以是O(1)。
3) 当update和查询的时候,都需要pushdown。
4) 注意先排序。

class Solution {
public:
    /**
     * @param n: length of sequence
     * @param q: Operating frequency
     * @param a: the sequence
     * @param b: the standard of each operation
     * @return: How many numbers are subtracted by one after each operation
     */
 void build(vector<int> &sum,vector<int> &a,int left,int right,int root)
    {
        if (left == right)
        {
            sum[root] = a[left - 1];
            return;
        }
        int mid = (left + right) / 2;
        build(sum, a, left, mid, 2 * root);
        build(sum, a, mid + 1, right, 2 * root + 1);
    }

    void pushDown(vector<int> &sum,vector<int> &add, int root, int left_num, int right_num)
    {
        if (add[root]) //如果存在标记就向下更新
        {
            add[2 * root] += add[root];  //累加
            add[2 * root + 1] += add[root];  //累加
            sum[2 * root] += add[root] * left_num;
            sum[2 * root + 1] += add[root] * right_num;
            add[root] = 0;
        }
    }

    void update(vector<int> &sum, vector<int> &add, int root_left, int root_right, int C, int query_left, int query_right, int root)
    {
        if (root_left <= query_left && query_right <= root_right)
        {
            sum[root] += C * (query_right - query_left + 1);
            add[root] += C;
            return;
        }
        int mid = (query_left + query_right) / 2;
        pushDown(sum, add, root, mid - query_left + 1, query_right - mid); //m - l + 1 and r - m are counts needs to add the delay tag
        if (root_left <= mid) update(sum, add, root_left, root_right, C, query_left, mid, 2 * root);
        if (root_right > mid) update(sum, add, root_left, root_right, C, mid + 1, query_right, 2 * root + 1);
    }
    
    int query(vector<int> &sum,vector<int> &add, int root_left, int root_right, int query_left, int query_right, int root)
    {
        if (root_left <= query_left && query_right <= root_right)
        {
            return sum[root];
        }
        int mid = (query_left + query_right) / 2;
        pushDown(sum, add, root, mid - query_left + 1, query_right - mid);
        int ans = 0;
        if (root_left <= mid) ans += query(sum, add, root_left, root_right, query_left, mid, 2 * root);
        if (root_right > mid) ans += query(sum, add, root_left, root_right, mid + 1, query_right, 2 * root + 1);
        return ans;
    }

    vector<int> sequenceMaintenance(int n, int q, vector<int> &a, vector<int> &b) {
        sort(a.begin(), a.end());
        vector<int> sum, add, ans;
        sum.assign(4 * n, 0);
        add.assign(4 * n, 0);
        
        build(sum, a, 1, n, 1);

        for (int i = 0; i < q; i++){
            int left = 1, right = n, pos = -1;
            
            while (left + 1 < right){
                int mid = (left + right) / 2;
                if (query(sum, add, mid, mid, 1, n, 1) >= b[i]){
                    right = mid;
                    pos = mid;
                } else {
                    left = mid;
                }
            }
            
            if (query(sum, add, right, right, 1, n, 1) >= b[i]){
                pos = right;
            }
            
            if (query(sum, add, left, left, 1, n, 1) >= b[i]){
                pos = left;
            }

            if (pos == -1) {
                ans.push_back(0);
            }
            else {
                ans.push_back( n - pos + 1);
                update(sum, add, pos, n, -1, 1, n, 1); //C = -1, root = 1
            }
        }

        return ans;
    }
};

解法2:类似解法1,但用经典树状结构

解法3:类似解法1,但采用非递归线段树实现。

TBD

解法4:树状数组也可以实现O(logn)的区间修改。
下面2个链接不错。
https://blog.youkuaiyun.com/ljw_study_in_优快云/article/details/100101852?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
https://www.cnblogs.com/dilthey/p/9366491.html

思路参考上面2个链接。主要是用C1[]和C2[]2个树状数组来实现。


class Solution {
public:
 
    /**
     * @param n: length of sequence
     * @param q: Operating frequency
     * @param a: the sequence
     * @param b: the standard of each operation
     * @return: How many numbers are subtracted by one after each operation
     */
     vector<int> sequenceMaintenance(int n, int q, vector<int> &a, vector<int> &b) {
        sort(a.begin(), a.end());
        N = a.size(), M = b.size();
        C1.resize(N + 1, 0);
        C2.resize(N + 1, 0);
        vector<int> ans;

        //initialize C1 and C2
        add(1, a[0]);
        for (int i = 2; i <= N; ++i) {
            add(i, a[i - 1] - a[i - 2]);
        }

        for (int i = 1; i <= M; ++i) {
            int pos = binarySearch(1, N, b[i - 1]);
			
            if (pos == -1) {
                ans.push_back(0);
            } else {
                ans.push_back(N - pos + 1);
                add_range(pos, N, -1);
        //        int range_sum = query_range(pos, N);
            }
        }

        return ans;
    }

private:
    int N, M;

    vector<long long> C1,C2; //分别记录 delta[i] 和 delta[i] * i
    
    int lowbit(int x) {
        return x & (-x);
    }

    void add(int pos, int val)
    {
        for(int i = pos; i <= N; i += lowbit(i)) {
            C1[i] += val;
            C2[i] += val * pos;
        }
    }
    
    //区间[l,r]加上x
    void add_range(int l, int r, int x) {
        add(l, x);
        add(r + 1,-x);
    }
    
    long long query(int pos) {
        long long ret = 0;
        for(int i = pos; i > 0; i -= lowbit(i)) {
            ret += (pos + 1) * C1[i] - C2[i];
        }
        return ret;
    }
    
    //查询区间[l,r]的和
    long long query_range(int l,int r) {
        long long result = query(r) - query(l - 1);
        return result;
    }
    
    int binarySearch(int start, int end, int target) {
        while (start + 1 < end){
              int mid = (start + end) / 2;
            if (query_range(mid, mid) >= target){
                end = mid;
            } else {
                start = mid;
            }
        }
        if (query_range(start, start) >= target) return start;
        if (query_range(end, end) >= target) return end;
        return -1;
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值