Educational Codeforces Round 76 (Rated for Div. 2) D(st表+二分)

本文解析了一道经典的勇士打怪算法题,涉及二分搜索、区间查询等数据结构和算法,通过实例详细阐述了解题思路和代码实现,适合算法初学者和竞赛选手参考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门

题意:

m m m只怪物,每只怪物有个 a a a值。 n n n个勇士,每个勇士有 p p p s s s,一天只能有一位勇士消耗其 s s s值去打怪,杀死一只怪消耗 1 1 1 s s s ( s − 1 ) (s-1) s1, 第二天所有勇士的 s s s值都会复原,问需要几天才能杀光所有的怪物。(还有一些细节没描述,大概这个题意。)

思路:

我们直接二分以 L L L为起点最多能杀多少只怪,假设为 m i d mid mid,那么我们对怪物的 p p p存个 s t st st表,直接查询 [ L , L + m i d − 1 ] [L, L+mid-1] [L,L+mid1]这个区间内怪物的最大值,然后再判断是否存在某个勇士的 p p p大于怪物的最大值,并且 s s s大于 m i d mid mid。 复杂度可能会达到 O ( n m l o g n ) O(nmlogn) Onmlogn

问题主要在判断勇士的时候,直接O(m)判断肯定会超时的。
我们可以先把勇士的 s s s从小到大排序,然后对 p p p求一个后缀最大值,二分坐标最小的,满足 s s s大于怪物最大值的坐标 i d id id, 然后判断 i d id id的后缀最大值是否大于 m i d mid mid。 因为后缀中,坐标越小最大值一定越大,因此如果存在某个坐标都满足条件了那就直接满足当前二分值。

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 2e5+5;

int dp[maxn][20],a[maxn],b[maxn];
struct node{
	int p,s;
	bool operator<(const node C)const{
		if(s == C.s) return p < C.p;
		return s < C.s;
	}
}w[maxn];
int n,m;
map<int,int>mp;
void RMQ(){
	for(int i = 1; i <= n; i++) dp[i][0] = a[i];
	for(int j = 1; j <= log(n)/log(2); j++)
		for(int i = 1; i <= n; i++)
			if(i+(1<<j)-1<=n)
				dp[i][j] = max(dp[i][j-1], dp[i+(1<<(j-1))][j-1]);
}

int query(int l,int r){
	int k = log(r-l+1)/log(2.0);
	return max(dp[l][k],dp[r-(1<<k)+1][k]);
}
bool check(int l,int r,int mid){
	int mx = query(l,r);
	int L = 1, R = m,id = 0;
	while(L <= R){
		int m = (L+R)>>1;
		if(w[m].s >= mid){
			id = m;
			R = m-1;
		}else L = m+1;
	}
	if(b[id] >= mx) return true;
	return false;
}
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		for(int i = 1; i <= n; i++)
			scanf("%d",&a[i]);
		scanf("%d",&m);
		for(int i = 1; i <= m; i++){
			scanf("%d%d",&w[i].p,&w[i].s);
		}
		for(int i = 1; i <= m+1; i++) b[i] = 0;
		sort(w+1,w+m+1);
		for(int i = m; i >= 1; i--){
			b[i] = max(b[i+1],w[i].p);
		}
		RMQ();
		int L = 1, R = n;
		int x = 0,ok = 1;;
		while(L <= R){
			int l = 1, r = R-L+1, ans = -1;
			while(l <= r){
				int mid = (l+r)>>1;
				
				if(check(L,L+mid-1,mid)){
					ans = mid;
					l = mid+1;
				}else r = mid-1;
			}
			if(ans == -1){
				puts("-1");
				ok = 0;
				break;
			}
			L = L + ans;
			x++;
		}
		if(ok)
		printf("%d\n",x);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值