题意:给一个含n(n<=200000)块矿石的序列,每块矿石有重量和价值。有m(m<=200000)个区间,对于某个w,每个区间的检验值为重量不小于w的矿石数量乘这些矿石的价值之和。总的检验值为各区间检验值之和。求检验值与标准值S之差的绝对值最小为多少。
检验值是w的函数,并且单调递减。因此,可以二分w,使检验值逼近S,间接地求得最小的差值。
如果检验值只定义为价值之和或数量,大概能迅速想到前缀和。但是它们乘在一起……我总想以某种方式重新组织求和顺序,比如统计一下这个点被多少个区间覆盖。但是,如果根据矿石求和,还需要知道该区间内其他点的情况。区间……是连续的……嗯,前缀和。
一开始的写法是分Y(m)>=S
和Y(m)<=S
两种来二分,可以合在一起。
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAX_M = 200000, MAX_N = 200000;
typedef long long ll;
const ll inf = 1LL<<60;
int n, m;
struct Segment {
int l, r;
} S[MAX_M+1];
struct Ore {
ll w, v;
} O[MAX_N+1];
ll Y(int w)
{
static ll sum[2][MAX_N+1];
memset(sum, 0, sizeof(sum));
for (int i = 1; i <= n; ++i)
if (O[i].w >= w) {
++sum[0][i];
sum[1][i] += O[i].v;
}
for (int i = 0; i < 2; ++i)
for (int j = 2; j <= n; ++j)
sum[i][j] += sum[i][j-1];
ll y = 0;
for (int i = 1; i <= m; ++i) {
int l = S[i].l, r = S[i].r;
y += (sum[0][r] - sum[0][l-1]) * (sum[1][r] - sum[1][l-1]);
}
return y;
}
int main()
{
ll a, mn = inf, mx = -inf;
scanf("%d %d %lld", &n, &m, &a);
for (int i = 1; i <= n; ++i) {
scanf("%lld %lld", &O[i].w, &O[i].v);
mn = min(mn, O[i].w);
mx = max(mx, O[i].w);
}
for (int i = 1; i <= m; ++i)
scanf("%d %d", &S[i].l, &S[i].r);
ll l = mn-1, r = mx+1, ans = inf;
while (r-l > 1) { // (l, r)
ll m = (l+r)/2, y = Y(m);
ans = min(ans, abs(y-a));
if (y > a)
l = m;
else if (y < a)
r = m;
else
break;
}
printf("%lld\n", ans);
return 0;
}