Forever Young

洛谷 P1314 聪明的质监员

题号挺不错/xyx

暴力 + 吸氧过了 \(75\) 分,然后用玄学优化,不知道为啥一直就 \(5\) 分、 \(10\) 分了,看来我还是太菜了

思路

二分答案+前缀和

显然计算一次 \(y=\sum\limits_{i=1}^{m}y_i\)可以用前缀和 \(O(n)\) 算出来。

现在要考虑的是如何快速找到一个 \(W\),能使 \(|s-y|\) 的值最小,由于计算检验值之和已经是 \(O(n)\) 的复杂度了,所以在这样的情况下最多允许找 \(O(\log n)\)\(W\) 的取值,这样代码的总复杂度是 \(O(n\log n)\) 的,是能过这道题的。

因为 \(W\) 越大,检验值的和 \(y\) 就越小,所以检验值的和是单调的,因此就可以想到二分答案,刚好复杂度就是 \(O(n\log n)\) 的,我们就找到了一种复合复杂度的做法。

接下来确定左右端点以及二分判定条件:

  • 左右端点显然可以用序列最小值和序列最大值
  • 在上面已经说过了,\(W\) 越大,\(y\) 就越小,所以判定条件就出来了
    • 如果 \(W=mid\) 时的权值 \(y > s\),那么\(l = mid +1\)
    • 否则 \(r = mid - 1\)
    • \(|s-y| < ans\) 时,更新 \(ans=|s-y|\)

这样就做完了

代码

/*
  Name: P1314 聪明的质监员
  Author: Loceaner
  Date: 30/08/20 18:05
  Description: 二分答案 
  Debug: 数据范围 
*/
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;

const int A = 2e5 + 11;
const int B = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

inline int read() {
  char c = getchar();
  int x = 0, f = 1;
  for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1;
  for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
  return x * f;
}

inline ll lread() {
  char c = getchar();
  ll x = 0, f = 1;
  for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1;
  for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
  return x * f;
}

int n, m, L[A], R[A];
ll s, val1[A], val2[A], w[A], v[A], maxn, minn;

ll calc(ll x) {
  ll sum = 0;
  for (int i = 1; i <= n; i++) {
    val1[i] = val1[i - 1];
    val2[i] = val2[i - 1];
    if (w[i] >= x) val1[i]++, val2[i] += v[i];
  }
  for (int i = 1; i <= m; i++)
    sum += (val1[R[i]] - val1[L[i] - 1]) * (val2[R[i]] - val2[L[i] - 1]);
  return sum;
}

int main() {
  n = read(), m = read(), s = lread();
  for (int i = 1; i <= n; i++) 
    w[i] = lread(), v[i] = lread(),
    maxn = max(maxn, w[i]), minn = min(minn, w[i]);
  for (int i = 1; i <= m; i++)
    L[i] = read(), R[i] = read();
  ll l = minn - 1, r = maxn + 1, ans = 1e12;
  while (l <= r) {
    ll mid = (l + r) >> 1;
    ll now = calc(mid);
    if (abs(s - now) <= ans) ans = abs(s - now);
    if (now > s) l = mid + 1; 
    else r = mid - 1;
  }
  cout << ans << '\n';
  return 0;
}
posted @ 2020-08-30 21:05  Loceaner  阅读(111)  评论(2编辑  收藏  举报