题意:
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) (s−1), 第二天所有勇士的 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+mid−1]这个区间内怪物的最大值,然后再判断是否存在某个勇士的 p p p大于怪物的最大值,并且 s s s大于 m i d mid mid。 复杂度可能会达到 O ( n m l o g n ) O(nmlogn) O(nmlogn)
问题主要在判断勇士的时候,直接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;
}