聪明的质检员

聪明的质检员

https://www.luogu.com.cn/problem/P1314

梳理题意:

  1. 给定矿石的数目n以及每个矿石的重量和价值;还定m个询问区间以及标准值s

  2. 要求对于每个区间,求出相应的Yi值;再求和算出总的Y值(W的值需要我们自己去枚举)

  • 计算公式:

将计算Yi的公式翻译为代码实现,就是:在每一个区间中寻找价值大于W的矿石,找到一个就用tot累加(注意:只是累加个数),再用tott累加价值;最后Yi=tot×tott

  1. 我们的总任务是:找出一个W,使得对应的Y与s的绝对值最小。最后输出最小的|s-Y|

做题历程:

  1. 读完题意,就想到暴力枚举:从0-n×m依次枚举W,在计算出对应的|s-Y|值,最后比较出最小输出即可。肯定会超时,但至少10分左右

  2. 但是交上去是0分。最开始是怀疑枚举的范围小了,就扩大范围枚举,还是0分

  3. 于是怀疑是将Yi的计算公式打错了。果然,前两次的代码将tot全部用作累加重量了!完全背离了题意。然后改了计算公式,交上去,有25分了,其他点全部超时。代码如下:

#include <bits/stdc++.h>
using namespace std;
long long n,m,s,maxn,sum=0x3f3f3f3f,l[200001],r[200001];
struct node {
	long long w,v;
}a[200001];

inline void solve(int x) {
	long long y=0;
	for(register int i=1;i<=m;i++) {
		long long tot=0,tott=0;
		for(register int j=l[i];j<=r[i];j++) {
			if(a[j].w>=x) {
				tot++;
				tott+=a[j].v;
			}
		}
		y=y+(tot*tott);
	}
	sum=min(sum,abs(y-s));
}

int main() {
	scanf("%lld%lld%lld",&n,&m,&s);
	for(register int i=1;i<=n;i++) {
		scanf("%lld%lld",&a[i].w,&a[i].v);
		maxn=max(maxn,a[i].w);
	}
	for(register int i=1;i<=m;i++) {
		scanf("%lld%lld",&l[i],&r[i]);
	}
	for(register int i=0;i<=maxn;i++) {
		solve(i);
	}
	printf("%lld",sum);
	return 0;
}
  1. 既然枚举有分,那就说明二分也能做且二分的得分会更高一些!那就将代码改为二分。二分代码,一般先直接套板子,再根据题意进行修改。交上去,有70分了!其他的还是超时问题。代码如下:
#include <bits/stdc++.h>
using namespace std;
long long n,m,s,ans=1e15,maxn,l[200010],r[200010];
struct node {
	long long w,v;
}a[200010];
int main() {
	scanf("%lld%lld%lld",&n,&m,&s);
	for(register int i=1;i<=n;i++) {
		scanf("%lld%lld",&a[i].w,&a[i].v);
		maxn=max(maxn,a[i].w);
	}
	for(register int i=1;i<=m;i++) {
		scanf("%lld%lld",&l[i],&r[i]);
	}
	long long L=0,R=maxn;
	while(L<=R) {
		long long y=0,mid=(L+R)>>1;
		for(register int i=1;i<=m;i++) {
			long long tot=0,tott=0;
			for(register int j=l[i];j<=r[i];j++) {
				if(a[j].w>=mid) {
					tot++;
					tott+=a[j].v;
				}
			}
			y+=(tot*tott);
		}
		if((s-y)<0) L=mid+1;
		else R=mid-1;
		ans=min(ans,abs(s-y));
	}
	printf("%lld",ans);
	return 0;
}
  1. 那剩下的问题就只有优化了。打开算法标签,发现还有一个是前缀和!那优化肯定就是前缀和了!看题解,只要加入一个前缀和记录每一个mid值对应的所有矿石的前缀和,就可以优化时间复杂度了。那么对应到二分程序中,就是将tot以及tott改为数组,计算Y值时运用差分(前缀和)即可。再交上去,就AC了。代码如下:
#include <bits/stdc++.h>
using namespace std;
long long n,m,s,ans=1e15,maxn,l[200010],r[200010],tot[200010],tott[200010];
struct node {
	long long w,v;
}a[200010];
int main() {
	scanf("%lld%lld%lld",&n,&m,&s);
	for(register int i=1;i<=n;i++) {
		scanf("%lld%lld",&a[i].w,&a[i].v);
		maxn=max(maxn,a[i].w);
	}
	for(register int i=1;i<=m;i++) {
		scanf("%lld%lld",&l[i],&r[i]);
	}
	long long L=0,R=maxn;
	while(L<=R) {
		long long y=0,mid=(L+R)>>1;
		for(register int i=1;i<=n;i++) {
			if(a[i].w>=mid) {
				tot[i]=tot[i-1]+1;
				tott[i]=tott[i-1]+a[i].v;
			}
			else {
				tot[i]=tot[i-1];
				tott[i]=tott[i-1];
			}
		}
		for(register int i=1;i<=m;i++) {
			y+=(tot[r[i]]-tot[l[i]-1])*(tott[r[i]]-tott[l[i]-1]);
		}
		if((s-y)<0) L=mid+1;
		else R=mid-1;
		ans=min(ans,abs(s-y));
	}
	printf("%lld",ans);
	return 0;
}

posted @ 2020-06-08 17:52  Eleven谦  阅读(157)  评论(0编辑  收藏  举报