【YbtOJ#20071】礼物购买

题目

题目链接:http://noip.ybtoj.com.cn/problem/20071

思路

(sum[i]) 表示按价格从大到小排序之后前 (i) 个物品的价格和。
然后假设当前有 (w) 元,要从第 (i) 个物品开始买,二分出第一个 (j) 使得 (sum[j]-sum[i-1]leq w),然后将 (isim j) 全部买下,剩余钱买尽量多的 (j+1)
加下来再次二分出第一个可以买的物品 (i'),继续上述操作即可。
假设我们有 (w) 元后一次操作变成了 (w') 元,当买的最后一个物品价格超过 (frac{w}{2}) 时,(w') 显然不超过 (w-) 价格,所以 (w'<frac{w}{2});当最后一个物品价格不超过 (frac {w}{2}) 时,因为买了最后一个物品之后买不了下一个物品了,所以剩余的钱一定不超过最后一个物品的价格,也就有 (w'leq frac{w}{2})
所以上述循环只会进行 (O(log w)) 次,时间复杂度 (O(mlog nlog w))

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=100010;
int n,m,ans,cnt[N];
ll w,sum[N],b[N];

struct node
{
	ll v,x;
}a[N];

bool cmp(node x,node y)
{
	return x.v>y.v;
}

int main()
{
	freopen("present.in","r",stdin);
	freopen("present.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	{
		scanf("%lld%lld",&a[i].v,&a[i].x);
		b[i]=a[i].v;
	}
	sort(b+1,b+1+n);
	sort(a+1,a+1+n,cmp);
	for (int i=1;i<=n;i++)
	{
		sum[i]=sum[i-1]+a[i].x*a[i].v;
		cnt[i]=cnt[i-1]+a[i].x;
	}
	sum[n+1]=b[n+1]=a[n+1].v=1e18;
	while (m--)
	{
		scanf("%lld",&w);
		ans=0;
		for (int i=1;i<=n;)
		{
			int r=upper_bound(sum+1,sum+2+n,sum[i-1]+w)-sum;
			w-=sum[r-1]-sum[i-1]; ans+=cnt[r-1]-cnt[i-1];
			ans+=w/a[r].v; w%=a[r].v;
			i=n-(upper_bound(b+1,b+n-r+1,w)-b-1)+1;
		}
		printf("%d
",ans);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/stoorz/p/13879494.html