【Gym - 102428C】 Cut Inequality Down(ST,二分,倍增)

Cut Inequality Down
题意:
某人在 n n n天中第 i i i天结束时可以获得金币数 a i a_i ai,但如果某天结束时金币数大于 U U U剩余金币数会变成 U U U,小于 L L L会变成 L L L
目前有 q q q组询问,每个询问有三个整数 B , E , X B,E,X B,E,X。表示在第 B B B天开始前有 X X X个金币,答案输出在第 E E E天结束时金币的个数。
n , q ≤ 1 e 5 , a i ≤ 2 e 6 n,q\le 1e5,a_i\le 2e6 n,q1e5,ai2e6

定义碰壁为金币数达到 U U U或者 L L L
对前缀和数组架ST表,维护区间最大值和最小值。我们可以通过二分求得在第 p o s pos pos天开始之前有 n o w now now个金币时,下次碰壁的是在第几天。
对于每一组询问,我们可以求出在 E E E之前最后一次碰壁的位置,剩下的天数直接通过前缀和求得答案。
但如果仅仅这么做复杂度还是很高,所以我们可以倍增加速寻找最后一次碰壁的过程。

n x t [ p o s ] [ 0 / 1 ] [ k ] nxt[pos][0/1][k] nxt[pos][0/1][k],表示在第 p o s pos pos天结束的时候碰到下壁or上壁的情况下,之后第 2 k 2^k 2k碰壁是在第几天。我们可以预处理这个数组里的内容。

总复杂度 O ( n l o g n + q l o g n ) O(nlogn+qlogn) O(nlogn+qlogn)

#include
#include
#include
#include
#include
#include
#include
#define LL long long
#define inl inline
#define re register
#define MAXN 101000
using namespace std;
int n;
LL L,U,a[MAXN];
LL sum[MAXN];
LL stmin[MAXN][17],stmax[MAXN][17];
LL getmin(int i,int j){int h=log2(j-i+1);return min(stmin[i][h],stmin[j-(1<<h)+1][h]);}
LL getmax(int i,int j){int h=log2(j-i+1);return max(stmax[i][h],stmax[j-(1<<h)+1][h]);}
int nxt[MAXN][2][17];
int jump(int pos,int sta,int k)
{
	if(pos>n)return nxt[pos][sta][k]=(n+1)*10;
	if(nxt[pos][sta][k])return nxt[pos][sta][k];
	int tmp=jump(pos,sta,k-1);
	return nxt[pos][sta][k]=jump(tmp/10,tmp&1,k-1);
}
int getnext(int pos,int now)
{
	int l=pos,r=n+1,mid;
	while(l<r)
	{
		mid=(l+r)>>1;
		LL mx=now+getmax(pos,mid)-sum[pos-1];
		LL mn=now+getmin(pos,mid)-sum[pos-1];
		if(mx>=U||mn<=L)r=mid;
		else l=mid+1;
	}
	if(l>n)return (n+1)*10;
	LL X=now+sum[l]-sum[pos-1];
	if(X<=L)return l*10;
	else return l*10+1;
}
int main()
{
	scanf("%d%lld%lld",&n,&L,&U);
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i],stmin[i][0]=stmax[i][0]=sum[i];
	for(int i=1;i<=16;i++)
		for(int j=1;(j+(1<<i))-1<=n;j++)
			stmin[j][i]=min(stmin[j][i-1],stmin[j+(1<<(i-1))][i-1]),
			stmax[j][i]=max(stmax[j][i-1],stmax[j+(1<<(i-1))][i-1]);
	for(int i=0;i<=n;i++)
		nxt[i][0][0]=getnext(i+1,L),nxt[i][1][0]=getnext(i+1,U);
	int T;cin>>T;
	while(T--)
	{
		int b,e;
		LL x;
		scanf("%d%d%lld",&b,&e,&x);
		int t=getnext(b,x);
		LL ans;
		if(t/10<e)
		{
			for(int i=16;i>=0;i--)
				if(jump(t/10,t&1,i)/10<=e)t=jump(t/10,t&1,i);
			if(t&1)ans=U+sum[e]-sum[t/10];
			else ans=L+sum[e]-sum[t/10];
			if(ans<L)ans=L;if(ans>U)ans=U;
		}
		else
		{
			ans=x+sum[e]-sum[b-1];
			if(ans<L)ans=L;if(ans>U)ans=U;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

你可能感兴趣的:(二分,倍增)