多校冲刺NOIP模拟17 - 宝藏——贪心、线段树二分

此题不提供链接

题目描述

在这里插入图片描述
在这里插入图片描述

题解

这题真的让我降智了。

一开始打了个 O ( n log ⁡ 2 n ) O(n\log ^2n) O(nlog2n) 的做法,想着回来再用zkw卡常就去看下一题了,结果最后没时间回来卡,加上大样例太水,MLE+WA+TLE。

首先可以看出,如果在开采量为 x x x 时中位数可以为 w i w_i wi,那么开采量 < x <x <x 时仍然可以。所以我们可以对每个数,求出这个数可做中位数的最大开采量,然后对贡献做一个后缀取 max ⁡ \max max 即可。

怎么求呢?初步想法就是把原数组按 w w w 排序,然后二分,判断取前面 t t t 最小的 x x x 个和后面 t t t 最小的 x x x 个是否合法。这个做法是 n log ⁡ 2 n n\log^2n nlog2n 的,后面的判断可以用线段树二分或平衡树。

但是实际上两个 log ⁡ \log log 过不去,除非二分剪枝优化加上zkw线段树还要疯狂卡一堆常。

我们可以按 w w w 从大到小倒着枚举每个数,那么这个数的合法最大开采量只有大于前面最大的合法开采量才有意义,否则一定不优。

所以我们不再需要二分了,因为最大开采量递增,直接枚举即可做到 O ( n log ⁡ n ) O(n\log n) O(nlogn)

代码

最快rank1代码,zkw线段树二分

(hs那个普通线段树4664ms的真的佩服,不知道怎么卡常的,可惜已经被我超过了)

#include<cstdio>//JZM yyds!!
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<ctime>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<random>
#define ll long long
#define uns unsigned
#define MOD 
#define MAXN 300005
#define INF 1e17
#define IF (it->first)
#define IS (it->second)
using namespace std;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0)f^=(s=='-'),s=getchar();
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
	return f?x:-x;
}
int pt[30],lp;
inline void print(ll x){
	if(x<0)putchar('-'),x=-x;
	pt[lp=1]=x%10;
	while(x>9)x/=10,pt[++lp]=x%10;
	while(lp)putchar(pt[lp--]^48);
}
inline ll lowbit(ll x){return x&-x;}
int n,Q;
ll T,ans[MAXN];
struct itn{
	ll w,t;itn(){}
	itn(ll W,ll T){w=W,t=T;}
	bool operator<(const itn&b)const{
		if(w^b.w)return w<b.w;
		else return t<b.t;
	}
}a[MAXN];

int p;
struct zkw{
	ll f[3000005];
	int g[3000005];
	inline void add(int x,ll df,int dg){
		for(x=p+x;x;x>>=1)f[x]+=df,g[x]+=dg;
	}
	inline ll sch(int k){
		if(g[1]<=k)return f[1];
		if(k<=0)return 0;
		int x=1;ll res=0;
		while(x<p){
			if(g[x<<1]>=k)x<<=1;
			else res+=f[x<<1],k-=g[x<<1],x=x<<1|1;
		}
		return res+(x-p)*k;
	}
}A,B;

inline ll check(int m){
	if(A.g[1]<m||B.g[1]<m)return T+1;
	return A.sch(m)+B.sch(m);
}
signed main()
{
	freopen("treasure.in","r",stdin);
	freopen("treasure.out","w",stdout);
	n=read(),T=read(),Q=read();
	memset(ans,-1,sizeof(ans));
	for(int i=1;i<=n;i++)a[i].w=read(),a[i].t=read();
	sort(a+1,a+1+n),p=1<<20;
	for(int i=1;i<n;i++)A.add(a[i].t,a[i].t,1);
	for(int i=n,j=0;i>0;i--){
		if(a[i].t<=T){
			int re=1;
			while(check(j)<=T-a[i].t)re=++j;
			ans[re]=max(ans[re],a[i].w);
			j=max(j,re);
		}
		B.add(a[i].t,a[i].t,1);
		if(i>1)A.add(a[i-1].t,-a[i-1].t,-1);
	}
	for(int i=n;i>0;i--)ans[i]=max(ans[i],ans[i+1]);
	while(Q--)print(ans[(read()+1)>>1]),putchar('\n');
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值