洛谷P1083 二分答案+差分

洛谷P1083 二分答案+差分

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

题目描述

题目传送门『P1083 借教室

数据范围:\(1\leq n,m\leq10^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(\text{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(\text{log}(m))\)
而每一次是使用差分优化,差分时间复杂度是\(O(n+m)\),因此时间复杂度是\(O(\text{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;
}
posted @ 2020-03-05 00:31  AdaMeta730  阅读(267)  评论(0编辑  收藏  举报