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\) , 以检查答案的优劣性
-
暴力枚举 \(!\)
对于 每一个所求区间 , 暴力枚举其中合法元素 , 并暴力计算复杂度 \(O(nm)\) , 必然会被卡掉
-
所求的 区间内合法元素数 及 区间内合法元素的和
显然 , 两量满足可减性 , 可以使用前缀和 进行维护则可在每次检查时,
使用前缀和 \(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));
}
作者@Luckyblock,转载请声明出处。