题目:https://www.luogu.org/problemnew/show/P1314
显然就是二分那个标准;
当然不能每个区间从头到尾算答案,所以要先算出每个位置被算了几次;
不知为何自己第一想法是把符合要求的位置插入树状数组再遍历区间得到该区间内的个数然后在其左右端点差分最后遍历位置时一边计算每个位置的次数;
但其实用前缀和就可以了...而且前缀和比上面那个快好多...
调了好半天,才发现 ans 的初值不能习惯性地赋成 0x3f3f3f3f,那个才是个 int 范围内的...
代码如下:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define mid ((L+R)>>1) using namespace std; typedef long long ll; int const maxn=2e5+5; int n,m,mx,w[maxn],v[maxn],f[maxn],l[maxn],r[maxn],d[maxn],num[maxn]; ll ans,s,sum[maxn]; ll rd() { ll ret=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=0; ch=getchar();} while(ch>='0'&&ch<='9')ret=(ret<<3ll)+(ret<<1ll)+ch-'0',ch=getchar(); return f?ret:-ret; } void add(int x){for(;x<=n;x+=(x&-x))f[x]++;} int query(int x){int ret=0; for(;x;x-=(x&-x))ret+=f[x]; return ret;} ll work(int x) { ll ret=0; // memset(f,0,sizeof f); // memset(d,0,sizeof d); // for(int i=1;i<=n;i++)if(w[i]>=x)add(i); // for(int i=1;i<=m;i++) // { // int k=query(r[i])-query(l[i]-1); // printf("l=%d r=%d k=%d ",l[i],r[i],k); // d[l[i]]+=k; d[r[i]+1]-=k; // } // for(int i=1,nw=0;i<=n;i++) // { // nw+=d[i]; // if(w[i]>=x)ret+=(ll)v[i]*nw; // printf("i=%d ret=%lld ",i,ret); // } memset(sum,0,sizeof sum); memset(num,0,sizeof num); for(int i=1;i<=n;i++) { num[i]=num[i-1]+(w[i]>=x); sum[i]=sum[i-1]+(w[i]>=x?v[i]:0); // printf("num[%d]=%d sum[%d]=%d ",i,num[i],i,sum[i]); } for(int i=1;i<=m;i++) ret+=(sum[r[i]]-sum[l[i]-1])*(num[r[i]]-num[l[i]-1]); return ret; } int main() { n=rd(); m=rd(); s=rd(); for(int i=1;i<=n;i++)w[i]=rd(),v[i]=rd(),mx=max(mx,w[i]); for(int i=1;i<=m;i++)l[i]=rd(),r[i]=rd(); int L=0,R=mx+1; ans=1e13; //ans=inf; while(L<=R) { ll ret=work(mid); // printf("ret=%lld mid=%d L=%d R=%d ",ret,mid,L,R); if(ret>=s) { // if(ans<=ret-s)break; ans=min(ans,ret-s); L=mid+1; } else { // if(ans<=s-ret)break; ans=min(ans,s-ret); R=mid-1; } } printf("%lld ",ans); return 0; }