P1314 聪明的质监员

知识点:二分答案,前缀和

原题面

题目要求:

给定 一序列 \(a\), 对于每个序列元素 有两个参数: \(w\)\(v\)
给定一参数 \(S\) , 给定 \(m\) 个区间 \([l_i,r_i]\)
可任意选择 参数 \(W\) 的值
总贡献为 :

\[\large Y = \sum\limits_{i=1}^{m}{(\sum\limits_j{1}\times {\sum\limits_j {v_j}})}(j\in[l_i,r_i], \text{且} w_j\ge W, j\text{为编号}) \]

求 最小的 \(\mid Y-S\mid\) 的值

分析题意:

  • 对于所求的 \(W\) , 发现如下性质:
    \(W\) 单增时, \(w_j\) 大于等于 \(W\) 的 元素数量 单减 , 总贡献 \(Y\) 单减
    \(W\) 单减时, \(w_j\) 大于等于 \(W\) 的 元素数量 单增 , 总贡献 \(Y\) 单增

    发现 \(W\) 的取值 与 \(Y\) 呈 负相关, 则可二分答案 枚举 \(W\) ;

  • \(W\) 确定时 , 考虑如何求得 \(Y\) , 以检查答案的优劣性

    1. 暴力枚举 \(!\)
      对于 每一个所求区间 , 暴力枚举其中合法元素 , 并暴力计算

      复杂度 \(O(nm)\) , 必然会被卡掉

    2. 所求的 区间内合法元素数 及 区间内合法元素的和
      显然 , 两量满足可减性 , 可以使用前缀和 进行维护

      则可在每次检查时,
      使用前缀和 \(O(n)\) 维护上述两量 ,
      则可 \(O(1)\) 求得每个区间内 两量, 就可 计算出 单个区间的贡献

      复杂度 \(O(n)\)


#include<cstdio>
#include<ctype.h>
#include<cstdlib>
#define max(a,b) (a>b?a:b)
#define int long long
const int MARX = 2e5+10;
//=============================================================
int n, m, S, ans, maxw , w[MARX], v[MARX];
int l[MARX], r[MARX];//查询的m个区间 
int sum1[MARX], sum2[MARX];
//sum1,2为 两前缀和数组, 1为满足条件的 矿石个数, 2为满足条件的 矿石 价值和 
//=============================================================
inline int read()
{
    int s=1, w=0; char ch=getchar();
    for(; !isdigit(ch); ch=getchar()) if(ch=='-') s =-1;
    for(; isdigit(ch); ch=getchar()) w = w*10+ch-'0';
    return s*w;
}
int abs(int x) {return (x<0?-x:x);}//绝对值 函数 
bool check(int W,int &sum)
{
	sum = 0;
	for(int i = 1; i <= n; i ++) //维护两前缀和 
	  sum1[i] = sum1[i-1] + (w[i] >= W),
	  sum2[i] = sum2[i-1] + v[i] * (w[i] >= W);
	
	for(int i = 1; i <= m; i ++)//枚举查询区间 
	{
	  int l1 = l[i], r1 = r[i];//计算 区间的贡献 
	  sum += (sum1[r1]-sum1[l1-1]) * (sum2[r1]-sum2[l1-1]);
	}
	return sum > S;//检查答案优劣性 
}
//=============================================================
signed main()
{
	n = read(),m = read(), S = read();
	for(int i = 1; i <= n; i ++)
	{
	   w[i] = read(), v[i] = read();
	   maxw = max(maxw,w[i]);
	}
	for(int i =1 ; i <= m; i ++) l[i] = read(), r[i] = read();
	
	for(int l = 1,r = maxw; l <= r;)//二分枚举 参数W 
	{
	  int mid = (l + r) >> 1, sum;
	  if(check(mid,sum)) l = mid + 1; 
	  else r = mid - 1;
	  if(abs(sum - S) < abs(ans - S)) ans = sum;//更新答案 
	}
	printf("%lld",abs(ans - S));
}
posted @ 2019-10-14 10:12  Luckyblock  阅读(93)  评论(0编辑  收藏  举报