• 洛谷P1083 二分答案+差分


    洛谷P1083 二分答案+差分

    算法关键词:二分答案、差分

    题目描述

    题目传送门『P1083 借教室

    数据范围:(1leq n,mleq10^6)
    题目意思:给定长度为(n)的数组,和(m)次询问,每次询问把数组([l,r])减去(d),问第几次询问能够使得数组能够存在小于0的数。

    朴素

    朴素做法:对当前(1..n)对每个询问区间做修改(闭区间([l,r])同时减去(d)),同时扫描当前区间长度([1,n])是否出现(a[i]leq0)
    不难分析时间复杂度是(O(nm)),而((10^6)^2>>10^7)必然超时。观察数据(1e6)的数据考虑(nlgn)做法。

    二分答案和差分思想

    时间复杂度(O( ext{log}(m)*(n+m)))

    具有二段性

    本题关键是如果第(k)个答案是不符合要求的,那么后面的都肯定不符合要求(后面减去上一些数,肯定也是小于0的)。即答案具有二段性。我们需要找到一个临界点,在这个临界点区间某个点下一次会被减到负数,而该点后面都是不满足的情况
    因此,我们需要实现一个函数check(m),使得询问当前这个点(m)是符合的(如果区间每一个数都是小于原来数组的那么是符合的),并且通过二分找到这个符合点的最右端点(k)

    差分思想优化区间修改的过程

    再次发现,每一次再修改一个区间都是同时加上或者减去某个数,最后会询问当前这个答案m下,是否是符合的,属于离线修改区间,可以考虑使用差分思想。
    方法是对于 ([l,r]) 加上(d)实质上是对于差分数组(p)(p[l]+d),(p[r+1]-d)
    即为:

      for (int i = l; i <= r; i++) {
        q[s[i]] += d[i];
        q[t[i] + 1] -= d[i];//做差分
      }
    

    最后对差分数组q求前缀和:

    for (int i = 1; i <= n; i++) q[i] = q[i] + q[i - 1];
    

    这样即可在(O(n+m))的时间复杂度内得到修改后的数组。
    而暴力是需要(O(m*n))的时间复杂度。

    二分答案过程使用差分思想优化后的时间复杂度

    因为总共有(m)次询问,二分([1,m])区间总共是(O( ext{log}(m)))
    而每一次是使用差分优化,差分时间复杂度是(O(n+m)),因此时间复杂度是(O( ext{log}(m)*(n+m)))
    综上比暴力(O(nm))快得多。

    代码

    //https://www.luogu.com.cn/problem/P1083
    // Author: oceanlvr
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    static int faster_iostream = []() {
      std::ios::sync_with_stdio(false);
      std::cin.tie(NULL);
      return 0;
    }();
    int n, m;
    const int maxn = 1e6 + 10;
    ll a[maxn];
    ll q[maxn];
    ll d[maxn], s[maxn], t[maxn];
    
    bool check(int idx) {
      memset(q, 0, sizeof q);
      int l = 1, r = idx;
      for (int i = l; i <= r; i++) {
        q[s[i]] += d[i];
        q[t[i] + 1] -= d[i];//做差分
      }
      //对差分做前缀和 得到最后m次修改之后的数组
      for (int i = 1; i <= n; i++) q[i] = q[i] + q[i - 1];
      for (int i = 1; i <= n; i++) {
        if (q[i] > a[i]) {
          return false;
        }
      }
      return true;  //满足分配条件
    }
    
    int main() {
      cin >> n >> m;
      for (int i = 1; i <= n; i++) cin >> a[i];
      for (int i = 1; i <= m; i++) cin >> d[i] >> s[i] >> t[i];
      //时间复杂度O(lg(m)*(m+n))
      int l = 1, r = m;
      while (l < r) {
        int mid = (l + r + 1) >> 1;
        if (check(mid)) {  //满足分配
          l = mid;         //试图找不满足
        } else {
          r = mid - 1;
        }
      }
      if (r == m) {
        cout << 0 << endl;
      } else if (check(r)) {
        cout << -1 << endl << r + 1 << endl;
      } else {
        //一个都不满足 此时r=1
        cout << -1 << endl << r << endl;
      }
      return 0;
    }
    
  • 相关阅读:
    单调栈的四种用法
    AtCoder Beginner Contest 223 F Parenthesis Checking(线段树)
    牛客寒假算法基础集训营4 B 进制(线段树)
    AtCoder Beginner Contest 243 E Edge Deletion(最短路)
    AtCoder Beginner Contest 238 E E Range Sums(建图)
    CodeForces 1632D New Year Concert(ST 表、二分)
    【windows terminal】cmd
    【vue3】监控响应: reactive/watch/watchEffect/provide.inject/computed
    Spring DM Application Context
    Android学习历程(一)结构理解
  • 原文地址:https://www.cnblogs.com/adameta/p/12417207.html
Copyright © 2020-2023  润新知