[JOISC 2024 Day1] 鱼 3

题目描述

给你一个初始为 0 0 0 的序列 a a a ,支持两种操作:

  1. 单点加 x x x x x x 为题目给出的定值)
  2. 后缀整体加1

给一个最终序列 b b b。有 Q Q Q 次询问,每次给定 l , r l,r l,r a l a_l al ~ a r a_r ar 是否能通过操作变为 b l b_l bl ~ b r b_r br。若可以,输出最少需要进行的 1 1 1 操作次数;否则输出 − 1 -1 1

1 ≤ N , Q ≤ 3 × 1 0 5 , 1 ≤ x , b ≤ 1 0 12 1 \leq N,Q \leq 3\times 10^5,1 \leq x,b \leq 10^{12} 1N,Q3×105,1x,b1012

题解

先考虑如果是 1 1 1 ~ n n n 怎么做。

由于结果与操作顺序无关,我们假设先进行若干次2操作再进行1操作。不难发现若干次2操作后序列一定单调不降,而且由于2操作的次数没有限制,所以我们理论上可以得到所有单调不降的非负整数数列,也就是说我们需要选一个整数数列,使其能通过最少的1操作变为最终序列。

思考了一会没啥想法,于是决定稍微转换一下思路,将初始序列设为 b b b,把两种操作都变成减,问题就变成了进行最少次的单点减 x x x 操作,使序列单调不降且所有元素非负。

想到这样一个暴力:遍历整个序列,对于序列中的每个数,若是比它的前一个数小,就把上一个数减 x x x,然后再退回上一个数进行考虑。但是这样复杂度显然是行不通的。

手玩一下不难发现,我们考虑一个数 a i a_i ai 时需要减 x x x 的必定是 1 1 1 i i i 的一个后缀,且这段后缀是每次操作后最长的保证任意相连两数之差不超过 x x x 的后缀。于是我们考虑用一个数据结构来维护这个序列:仍然是遍历序列考虑对每个数操作,若是当前 a i ≥ a i − 1 a_i≥a_{i-1} aiai1 则跳过,否则直接算出 a i − 1 a_{i-1} ai1 至少需要减几次,再对满足 最长的保证任意相连两数之差不超过 x x x 的后缀 进行区间减,每次操作完后判断一下 a 1 a_1 a1 是否非负即可(因为每次操作完后序列单调不降所以只需判断第一个数)。

然后考虑怎么快速求出满足上面那个条件的后缀,发现只需要每次考虑完一个数后判断下当前这个数跟上个数之差是否小于 x x x、刚操作完的后缀的第一个数跟上个数之差是否小于 x x x,若是则合并两个区间。操作时判断一下是否需要合并即可。

然后考虑有询问怎么做。

发现我们遍历过程中答案是单调的,于是我们把询问离线下来按右端点排序,再多维护一个每个数减 x x x 的次数,然后区间求和即可。

调了蛮久的,感觉还是有不少细节?

Code

#include<bits/stdc++.h>
#define ls p<<1
#define rs p<<1|1
using namespace std;

const int N=3e5+5;
typedef long long ll;

int n,m,pos=1,f[N];
ll k,a[N],b[N],ans[N];

struct query{
	int x,y,id;
}q[N];

bool cmp(query a,query b){
	return a.y<b.y;
}

int find(int x){
	return f[x]==x?x:f[x]=find(f[x]);
}

struct segment_tree{
	ll mn[N<<2],sum[N<<2],laz[N<<2];
	
	void pushup(int p){
		sum[p]=sum[ls]+sum[rs];
		mn[p]=min(mn[ls],mn[rs]);
	}
	
	void build(int p,int l,int r){
		if(l==r){
			mn[p]=a[l];
			return;
		}
		int mid=l+r>>1;
		build(ls,l,mid);
		build(rs,mid+1,r);
		pushup(p);
	}
	
	void pushdown(int p,int l,int r){
		int mid=l+r>>1;
		laz[ls]+=laz[p],laz[rs]+=laz[p];
		sum[ls]+=(mid-l+1)*laz[p];
		sum[rs]+=(r-mid)*laz[p];
		mn[ls]-=laz[p]*k;
		mn[rs]-=laz[p]*k;
		laz[p]=0;
	}
	
	void update(int p,int l,int r,int a,int b,ll c){
		if(a<=l&&b>=r){
			sum[p]+=(r-l+1)*c;
			laz[p]+=c;
			mn[p]-=c*k;
			return;
		}
		if(laz[p]) pushdown(p,l,r);
		int mid=l+r>>1;
		if(a<=mid) update(ls,l,mid,a,b,c);
		if(b>mid) update(rs,mid+1,r,a,b,c);
		pushup(p);
	}
	
	ll query(int p,int l,int r,int a,int b){
		if(a<=l&&b>=r){
			if(mn[p]<0) return -1;
			return sum[p];
		}
		if(laz[p]) pushdown(p,l,r);
		int mid=l+r>>1;
		ll res=0,x;
		if(a<=mid){
			x=query(ls,l,mid,a,b);
			if(x==-1) return -1;
			res+=x;
		}
		if(b>mid){
			x=query(rs,mid+1,r,a,b);
			if(x==-1) return -1;
			res+=x;
		}
		return res;
	}
}t;

int main(){
	scanf("%d%lld",&n,&k);
	for(int i=1;i<=n;i++)
		f[i]=i;
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	t.build(1,1,n);
	scanf("%d",&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d",&q[i].x,&q[i].y);
		q[i].id=i;
	}
	sort(q+1,q+1+m,cmp);
	for(int i=1;i<=n;i++){
		if(a[i]<a[i-1]){
			int x=find(i-1);
			ll z=ceil((a[i-1]-a[i])*1.0/k);
			f[i]=x;
			while(b[x]<=z){
				z-=b[x];
				t.update(1,1,n,x,i-1,b[x]);
				b[x]=0;
				if(x-1>0) f[x]=find(x-1);
				else break;
				x=find(x);
			}
			t.update(1,1,n,x,i-1,z);
			b[x]-=z;
		}
		else{
			b[i]=(a[i]-a[i-1])/k;
			if(b[i]==0) f[i]=find(i-1);
		}
		while(q[pos].y==i&&pos<=m){
			ans[q[pos].id]=t.query(1,1,n,q[pos].x,q[pos].y);
			pos++;
		}
	}
	for(int i=1;i<=m;i++)
		printf("%lld\n",ans[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值