这道题很多OJ上都有,例如洛谷.
自己找题目吧.
这道题很容易想到枚举W,但是会TLE.
所以很容易想到二分,而可二分性的证明就很简单了.
W越大,Y就越小.
因为所有数都为正整数,所以vj肯定是大于0的.
所以j的个数也是正的加上去.
而W越大说明满足条件的j越少,所以Yi就越小,那么Y也就越小.
那么可二分了.
那么二分的check函数写出来(是个long long),就很容易了.
我们直接暴力,时间复杂度为O(n^2*log(n)).
那么我们分析一下这题的性质,那么我们可以用前缀和来处理,直接有条件的前缀和处理每一块矿石.
那么有条件的前缀和就是每一次check都重求一遍,当这块矿石满足了条件才能加进来,否则不加.
同时也要处理一个num数组,其中num[i]表示矿石1~i中有几个满足要求.
那么前缀和就这么处理:
ll Y=0; A[0]=0;num[0]=0; for (ll i=1;i<=n;i++) if (a[i].w>=W) A[i]=A[i-1]+a[i].v,num[i]=num[i-1]+1; else A[i]=A[i-1],num[i]=num[i-1];
然后暴力枚举每个区间就行了:
for (ll i=1;i<=m;i++) Y+=(num[e[i].r]-num[e[i].l-1])*(A[e[i].r]-A[e[i].l-1]); return Y;
那么整个check函数如下:
ll A[M+1]; ll num[M+1]; ll check(ll W){ ll Y=0; A[0]=0;num[0]=0; for (ll i=1;i<=n;i++) if (a[i].w>=W) A[i]=A[i-1]+a[i].v,num[i]=num[i-1]+1; else A[i]=A[i-1],num[i]=num[i-1]; for (ll i=1;i<=m;i++) Y+=(num[e[i].r]-num[e[i].l-1])*(A[e[i].r]-A[e[i].l-1]); return Y; }
那么AC正解就很简单了,套个二分就行了:
#include<bits/stdc++.h> using namespace std; #define rep(i,j,k) for (ll i=j;i<=k;i++) typedef long long ll; const ll M=200000; ll n,m,S; ll minn=M*M*1000; struct none{ ll w,v; }a[M+1]; struct seg{ ll l,r; }e[M+1]; inline void into(){ scanf("%lld%lld%lld",&n,&m,&S); rep(i,1,n) scanf("%lld%lld",&a[i].w,&a[i].v); rep(i,1,m) scanf("%lld%lld",&e[i].l,&e[i].r); } ll A[M+1]; ll num[M+1]; ll check(ll W){ ll Y=0; A[0]=0;num[0]=0; for (ll i=1;i<=n;i++) if (a[i].w>=W) A[i]=A[i-1]+a[i].v,num[i]=num[i-1]+1; else A[i]=A[i-1],num[i]=num[i-1]; for (ll i=1;i<=m;i++) Y+=(num[e[i].r]-num[e[i].l-1])*(A[e[i].r]-A[e[i].l-1]); return Y; } inline void work(){ ll l=1,r=200000,mid=(l+r)>>1; while (l+1<r){ if (check(mid)<=S) r=mid; else l=mid; mid=(l+r)>>1; } minn=min(minn,abs(check(l)-S)); minn=min(minn,abs(check(r)-S)); } inline void outo(){ printf("%lld\n",minn); } int main(){ into(); work(); outo(); return 0; }
好了.
这题简单.