首先看单次询问我们怎么做。对于一个人,他的最优策略显然是不断吃最小的,并看最后能不能吃完。
假设我们把区间内的数排好序了,设为
a
1
≤
a
2
≤
⋯
≤
a
n
a_1\leq a_2\leq \cdots\leq a_n
a1≤a2≤⋯≤an。对于一个
u
u
u,它能吃完所有的人当且仅当:
∀
i
<
u
,
a
u
+
∑
j
=
1
i
−
1
a
j
−
a
i
≥
k
∀
i
>
u
,
∑
j
=
1
i
−
1
a
j
−
a
i
≥
k
\begin{aligned} \forall i<u,a_u+\sum_{j=1}^{i-1}a_j-a_i\geq k\\ \forall i>u,\sum_{j=1}^{i-1}a_j-a_i\geq k \end{aligned}
∀i<u,au+j=1∑i−1aj−ai≥k∀i>u,j=1∑i−1aj−ai≥k
对于第一个条件
∀
i
<
u
,
a
u
≥
k
+
a
i
−
∑
j
=
1
i
−
1
a
j
\forall i<u,a_u\geq k+a_i-\sum_{j=1}^{i-1}a_j
∀i<u,au≥k+ai−∑j=1i−1aj,我们考虑维护出
a
i
−
∑
j
=
1
i
−
1
a
j
a_i-\sum_{j=1}^{i-1} a_j
ai−∑j=1i−1aj 的前缀最值的位置(即类似一个单调栈的东西),那么对于同一段内的限制都是相同的。
注意到这样的前缀最值个数不是很多:设上一次前缀最值的位置为 l s t lst lst,那么 i i i 成为新的前缀最值需要满足 a i − ∑ j = 1 i − 1 a j > a l s t − ∑ j = 1 l s t − 1 a j → a i > a l s t + ∑ j = l s t i − 1 a j a_i-\sum_{j=1}^{i-1}a_j>a_{lst}-\sum_{j=1}^{lst-1}a_j\to a_i>a_{lst}+\sum_{j=lst}^{i-1}a_j ai−∑j=1i−1aj>alst−∑j=1lst−1aj→ai>alst+∑j=lsti−1aj,也就是说 a i a_i ai 至少会翻倍,那么前缀最值个数的一个上界为 O ( log V ) O(\log V) O(logV)。
但直接找到前缀最值是困难的,不过发现我们可以放宽一点限制:我们可以找到 a i − ∑ j = 1 i − 1 a j a_i-\sum_{j=1}^{i-1}a_j ai−∑j=1i−1aj 上升的位置,即 a i − ∑ j = 1 i − 1 a j > a i − 1 − ∑ j = 1 i − 2 a j → a i > 2 a i − 1 a_i-\sum_{j=1}^{i-1}a_j>a_{i-1}-\sum_{j=1}^{i-2}a_j\to a_i>2a_{i-1} ai−∑j=1i−1aj>ai−1−∑j=1i−2aj→ai>2ai−1。满足这个条件的 a i a_i ai 同样只有至多 O ( log V ) O(\log V) O(logV) 个,而且可以用主席树挨个求出。
第二个条件是与 u u u 无关的,那么我们可以先找到最大的 p p p 满足 ∑ j = 1 p − 1 a j − a p < k \sum_{j=1}^{p-1}a_j-a_p<k ∑j=1p−1aj−ap<k,那么对于任意 u < p u<p u<p 它们都不是答案,对于任意 u ≥ p u\geq p u≥p 我们只需关注第一个条件。
发现在解决第一个条件的过程中,我们已经找到了 a i − ∑ j = 1 i − 1 a j a_i-\sum_{j=1}^{i-1}a_j ai−∑j=1i−1aj 上升的所有位置,而我们要找的是满足 a p − ∑ j = 1 p − 1 a j > − k a_p-\sum_{j=1}^{p-1}a_j>-k ap−∑j=1p−1aj>−k 的最大的 p p p,于是我们就已经可以锁定 p p p 在哪两个上升位置之间,而这两个位置之间的 a i − ∑ j = 1 i − 1 a j a_i-\sum_{j=1}^{i-1}a_j ai−∑j=1i−1aj 是单降的,所以可以通过二分求 p p p。
而对于一个前缀最值段内,对 a u a_u au 的限制都是一样的,容易用主席树求出合法的 u u u 的个数。
总时间复杂度 O ( n log n + q log V log n ) O(n\log n+q\log V\log n) O(nlogn+qlogVlogn)。
接下来介绍蒋老师的单 log \log log 做法:
首先应注意到单调性,即若 i i i 最后能获胜,则 ≥ i \geq i ≥i 的人最后也一定能获胜。这样我们只需要找第一个能获胜的人 u u u。
用的是值域分段的技巧,我们将 [ 2 b , 2 b + 1 ) [2^b,2^{b+1}) [2b,2b+1) 分成一段。那么仍然考虑 u u u 能吃完所有人的条件,发现:
- 对于非 u u u 所在的段,只要 u u u 能够吃掉该段的段头(出现在该段中的最小的数), u u u 就能吃掉这一整段。
- 对于 u u u 所在的段:若 u u u 不是段头,则条件和上一条相同;若 u u u 是段头,只要 u u u 能够吃掉该段第二个数, u u u 就能吃掉这一整段。
同样,对于 u u u 后面的那些段,能吃掉段头的限制是和 u u u 无关的。那么我们可以先找到最后一个吃不掉段头的段,设为第 T T T 段,那么 u u u 就必须在第 T T T 段及之后,且此时我们无需再考虑 u u u 之后的段的限制。
那么我们考虑从前往后枚举 u u u 所在的段,对于 u u u 为段头的情况我们单独考虑,对于 u u u 非段头的情况,根据前面的段和当前段,我们会得到一个形如 a u ≥ x a_u\geq x au≥x 的限制,此时只需要看该段中最大的数是否 ≥ x \geq x ≥x:若 ≥ x \geq x ≥x,则能获胜的人就是所有 ≥ x \geq x ≥x 的人;若 < x <x <x,则继续考虑 u u u 是否在下一段。
我们只需要支持一些主席树上的操作,以及对每一段的区间求和、区间 min 和次 min、区间 max,使用 st 表即可做到 O ( ( n + q ) ( log n + log V ) ) O((n+q)(\log n+\log V)) O((n+q)(logn+logV))。
#include<bits/stdc++.h>
#define N 200010
#define ll long long
#define INF 0x7fffffff
#define fi first
#define se second
#define pii pair<int,int>
#define mk(a,b) make_pair(a,b)
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^'0');
ch=getchar();
}
return x*f;
}
const int B=30;
inline int hb(int x){return 31-__builtin_clz(x);}
int n,q,a[N];
namespace Seg
{
#define lc(u) ch[u][0]
#define rc(u) ch[u][1]
const int nn=1e9;
const int NN=10000000;
int node,rt[N],ch[NN][2],size[NN];
inline void update(int &u,int lst,int l,int r,int x)
{
u=++node,lc(u)=lc(lst),rc(u)=rc(lst),size[u]=size[lst]+1;
if(l==r) return;
int mid=(l+r)>>1;
if(x<=mid) update(lc(u),lc(lst),l,mid,x);
else update(rc(u),rc(lst),mid+1,r,x);
}
inline int query(int a,int b,int l,int r,int x)
{
if(x<=l) return size[b]-size[a];
int mid=(l+r)>>1,ans=0;
if(x<=mid) ans+=query(lc(a),lc(b),l,mid,x);
return ans+query(rc(a),rc(b),mid+1,r,x);
}
void init()
{
for(int i=1;i<=n;i++)
update(rt[i],rt[i-1],1,nn,a[i]);
}
int query(int l,int r,int x){return query(rt[l-1],rt[r],1,nn,x);}
#undef lc
#undef rc
}
struct data
{
pii min1,min2;
data(){}
data(int p){min1=mk(a[p],p),min2=mk(INF,114514);}
data(pii x,pii y){min1=x,min2=y;}
};
inline data max(const data &a,const data &b)
{
if(a.min1<b.min1) return data(a.min1,min(a.min2,b.min1!=a.min1?b.min1:b.min2));
return data(b.min1,min(b.min2,a.min1!=b.min1?a.min1:a.min2));
}
template <class T> T Set(int p);
template<> int Set<int>(int p){return a[p];}
template<> data Set<data>(int p){return data(p);}
template<class T>
struct ST
{
vector<vector<T>> maxn;
void init(const vector<int> &pos)
{
const int nn=pos.size();
maxn.resize(nn);
for(int i=nn-1;i>=0;i--)
{
maxn[i].resize(hb(nn-i)+1);
maxn[i][0]=Set<T>(pos[i]);
for(int j=0;i+(1<<(j+1))-1<nn;j++)
maxn[i][j+1]=max(maxn[i][j],maxn[i+(1<<j)][j]);
}
}
inline T query(int l,int r)
{
const int b=hb(r-l+1);
return max(maxn[l][b],maxn[r-(1<<b)+1][b]);
}
};
struct block
{
int nn,lef[N],rig[N];
vector<int> pos;
ST<int> maxn;
ST<data> minn;
vector<ll> sum;
void init()
{
nn=pos.size();
static int vis[N];
memset(vis,-1,sizeof(vis));
for(int i=0;i<nn;i++) vis[pos[i]]=i;
for(int i=1,lst=-1;i<=n;lef[i]=lst,i++) if(~vis[i]) lst=vis[i];
for(int i=n,lst=nn;i>=1;rig[i]=lst,i--) if(~vis[i]) lst=vis[i];
maxn.init(pos),minn.init(pos);
sum.resize(nn);
for(int i=0;i<nn;i++) sum[i]=a[pos[i]]+(i?sum[i-1]:0);
}
inline bool empty(int l,int r){return rig[l]>lef[r];}
inline int querymax(int l,int r){return maxn.query(rig[l],lef[r]);}
inline data querymin(int l,int r){return minn.query(rig[l],lef[r]);}
inline ll querysum(int l,int r){return sum[lef[r]]-(rig[l]?sum[rig[l]-1]:0);}
}b[B];
int query(int l,int r,int k)
{
int T=0;
ll sum=0;
for(int i=0;i<B;i++)
{
if(b[i].empty(l,r)) continue;
int head=b[i].querymin(l,r).min1.fi;
if(sum<head+k) T=i;
sum+=b[i].querysum(l,r);
}
ll premax=-1e15; sum=0;
for(int i=0;i<T;i++)
{
if(b[i].empty(l,r)) continue;
int head=b[i].querymin(l,r).min1.fi;
premax=max(premax,head-sum);
sum+=b[i].querysum(l,r);
}
int X=-1;
for(int i=T;i<B;i++)
{
if(b[i].empty(l,r)) continue;
data minn=b[i].querymin(l,r);
int head=minn.min1.fi,sec=minn.min2.fi;
if(sec==INF)
{
if(head>=premax+k)
{
X=head;
break;
}
premax=max(premax,head-sum);
sum+=head;
continue;
}
if(head>=premax+k&&sum+head>=sec+k)
{
X=head;
break;
}
premax=max(premax,head-sum);
sum+=b[i].querysum(l,r);
int maxn=b[i].querymax(l,r);
if(maxn>=premax+k)
{
X=max(sec,(int)premax+k);
break;
}
}
if(X==-1) return 0;
return Seg::query(l,r,max(X,1));
}
int main()
{
n=read(),q=read();
for(int i=1;i<=n;i++)
a[i]=read(),b[hb(a[i])].pos.push_back(i);
for(int i=0;i<B;i++) b[i].init();
Seg::init();
while(q--)
{
int l=read(),r=read(),k=read();
printf("%d\n",query(l,r,k));
}
return 0;
}
/*
6 4
3 1 5 3 7 5
4 6 4
*/
/*
3 2
3 3 3
1 3 1
1 3 0
*/