2 条题解
-
2
吐槽一下,这个质检员不会被开除么。。。。
这个题很明显的二分枚举,但是还有一个前缀和有点坑人。
这题题其实点不多,就两个关键点: 第一:二分的判断。
可以看到:在W取0时,所有的区间内的矿石都可以选上,
而在W大于最大的质量时,所有的矿石都选不上。
然后简单算一下就发现:
W越大,矿石选的越少,W越小,矿石选的越多。 所以,随着W增大,Y值减小;
所以:二分的判断条件出来了:
当Y>sY>s 时,需要增大WW来减小YY,从而∣Y−s∣∣Y−s∣变小;
当YsYs时,∣Y−s∣0∣Y−s∣0;
当Y<sY<s时,需要减小W来增大YY,从而∣Y−s∣∣Y−s∣变大; 第二:前缀和。
我们在计算一个区间的和时(虽然这里是两个区间和再相乘,但没关系)
通常是用前缀和的方法来缩减时间,直接模拟是n2n2的,而前缀和成了2∗n2∗n
大大的优化了时间,前缀和不会的去先学前缀和,我默认大家都会,就不赘述了。
很显然:
在w[i]>=Ww[i]>=W时这个i矿石会在统计里(若<W<W就不管它了直接pre[i]=pre[i−1]pre[i]=pre[i−1]),
矿石价值和是:prev[i]=prev[i−1]+v[i]prev[i]=prev[i−1]+v[i],前面的和加上当前这一个i矿石;
矿石数量和是:pren[i]=pren[i−1]+1pren[i]=pren[i−1]+1,数量加1嘛。
然后最后算的时候用右端点r-(左端点l-1)就可以了 注意:谨记所前缀和时要pre[r]-pre[l-1],这个‘-1’不要忘!
然后就没啥了,给上代码:
(当然,一些基础的“lld”之类的你要注意,不多赘述了)
#include<bits/stdc++.h> using namespace std; const int maxn=200010; int w[maxn],v[maxn],l[maxn],r[maxn]; long long pre_n[maxn],pre_v[maxn]; long long Y,s,sum; int n,m,mx=-1,mn=2147483647; bool check(int W) { Y=0,sum=0; memset(pre_n,0,sizeof(pre_n)); memset(pre_v,0,sizeof(pre_v)); for(int i=1;i<=n;i++) { if(w[i]>=W) pre_n[i]=pre_n[i-1]+1,pre_v[i]=pre_v[i-1]+v[i]; else pre_n[i]=pre_n[i-1],pre_v[i]=pre_v[i-1]; } for(int i=1;i<=m;i++) Y+=(pre_n[r[i]]-pre_n[l[i]-1])*(pre_v[r[i]]-pre_v[l[i]-1]); sum=llabs(Y-s); if(Y>s) return true; else return false; } int main(){ // freopen("qc.in","r",stdin); // freopen("qc.out","w",stdout); scanf("%d %d %lld",&n,&m,&s); for(int i=1;i<=n;i++) { scanf(" %d %d",&w[i],&v[i]); mx=max(mx,w[i]); mn=min(mn,w[i]); } for(int i=1;i<=m;i++) scanf(" %d %d",&l[i],&r[i]); int left=mn-1,right=mx+2,mid; //这里有的人说要特判左右端点的check,但是其实你把left开成mn-1,right开成mx+2(注意取mx+1时即为W比所有都大,Y是0,这个情况要考虑,所以+2包含mx+1)就可以包含左右端点的check了,会简单点。 long long ans=0x3f3f3f3f3f3f3f3f;//ll 范围内的无穷大,近似于(maxll/2)的大小 while(left<=right) { mid=(left+right)>>1; if(check(mid)) left=mid+1; else right=mid-1; if(sum<ans) ans=sum; } printf("%lld",ans); return 0; }
-
0
二分+前缀和
这道题让我深刻感受到了前缀和的灵活性,
和对二分的恨。我有一点不解:按理来说(sums>s) 等价于 (sums-s)
,但是代码中如果换成后者就wa了。😕 具体题解楼上说过了。那我就分析一下题目好了:那一长串公式实际就是有一个不确定的W。然后在一个区间内(区间为矿石的编号,端点可取)找到重量>=W的所有矿石,然后将他们的数量*他们所有价值之和,得到一个y值。然后像这样计算所有区间的y后把他们加在一起得到sum。最后的检验值就是|sum-s|。 难点在于对前缀和的运用和理解,这是个好东西。真正理解后你会对前缀和有一个更深刻的认识。#include<bits/stdc++.h> using namespace std; #define ll long long ll n,m,s,A[200010][2],B[200010][2],minn=LONG_LONG_MAX,maxx=-1,Asum[200010][2]={0},ans = LONG_LONG_MAX ,sum,sums; bool check(ll k){ sums=0,sum=0; memset(Asum,0,sizeof(Asum)); for(int i=1;i<=n;i++){ //前缀和的计算 if(A[i][0] >= k){ Asum[i][0] = Asum[i-1][0] + 1; Asum[i][1] = Asum[i-1][1] + A[i][1]; } else{ Asum[i][0] = Asum[i-1][0]; Asum[i][1] = Asum[i-1][1]; } } for(int i=1;i<=m;i++){ sums += ( Asum[B[i][1]][0] - Asum[B[i][0]-1][0] )*( Asum[B[i][1]][1] - Asum[B[i][0]-1][1] ); } sum = abs(sums-s); if(sums>s) return 1; return 0; } int main(){ scanf("%lld%lld%lld",&n,&m,&s); for(int i=1;i<=n;i++){ scanf("%lld%lld",&A[i][0],&A[i][1]); minn = min(minn,A[i][0]); maxx = max(maxx,A[i][0]); } for(int i=1;i<=m;i++){ scanf("%lld%lld",&B[i][0],&B[i][1]); } ll l = minn-1, r = maxx+2,mid=0; while(l<=r){ //二分 mid = (l + r) >> 1; if(check(mid)) l = mid + 1; else r = mid - 1; ans = min(ans,sum); } printf("%lld",ans); return 0; }
- 1
信息
- ID
- 1545
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 5
- 标签
- 递交数
- 74
- 已通过
- 26
- 上传者