CF803G Periodic RMQ Problem

CF803G Periodic RMQ Problem

题目大意

给你一个序列 a a a,序列 b b b a a a 重复拼接 k k k 次后的结果。现要你实现区间赋值区间最值。

Solution

一道氵黑吧,CF 只有 *2300。

首先考虑维护,一个非常简单的线段树,基操了。

但是这道题唯一的困难之处就是无法在拼接后的长度为 1 0 9 10^9 109 的序列中用线段树。

然后我们发现询问只有 1 0 5 10^5 105 个,于是我们考虑放弃掉一些无用的数据。

第一个想到的应该是只保留每次询问和修改的两个端点。想做到这样并不难,先离线询问,然后我们对每一对 l , r l,r l,r 离散化一下,那就只有 2 × Q 2\times Q 2×Q 个点,然后我们对于这 2 × Q 2\times Q 2×Q 个点来做线段树,仿佛非常可行的鸭子。

但是我们来考虑这样一个情况:

5 3
5 6 2 3 6
3
2 1 5
1 2 3 4
2 1 5

好我们按照刚才的想法,保留下来的点是 5    6    2    6 5\;6\;2\;6 5626。然后执行第一个询问结果是 2 2 2 没错,执行赋值后区间变成了 5    4    4    6 5\;4\;4\;6 5446 于是我们回答下一个询问的答案是 4 4 4 而我们希望的答案是 3 3 3

原因就是,我们在保留的时候却丢失了 3 3 3 的信息,于是我们联想到,在我们上面保留的点的基础上,相邻两个已经保留的点之间的最小值也是需要的(但不需要整段的信息,因为不会有询问只询问这一段的一半,或者是修改一半,不然这一段就会被分成两段甚至更多),所以在上面的基础之上,我们把相邻两个点之间的最小值也插入进去做线段树就没问题了。

所以空间要开 4 4 4 倍以上[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-48aKKPNI-1631788774478)(\https://啧.tk/kk)]别问问就是 RE 人。

Code

#include<bits/stdc++.h>
#define ll long long
#define inf (1<<30)
#define INF (1ll<<60)
using namespace std;
const int MAXN=5e5+10;
struct Tree{
	int l,r;
	ll mn,inc;
}tr[MAXN<<2];
#define ls i<<1
#define rs i<<1|1
ll b[MAXN];
void pushup(int i){
	tr[i].mn=min(tr[ls].mn,tr[rs].mn);
}
void build(int i,int l,int r){
	tr[i].l=l;tr[i].r=r;tr[i].inc=-1;
	if(l==r){tr[i].mn=b[l];return;}
	int mid=l+r>>1;build(ls,l,mid);build(rs,mid+1,r);
	pushup(i);
}
void cov(int i,ll v){
	tr[i].mn=v;tr[i].inc=v;
}
void pushdown(int i){
	if(tr[i].inc==-1) return;
	cov(ls,tr[i].inc);
	cov(rs,tr[i].inc);
	tr[i].inc=-1;
}
void upd(int i,int l,int r,ll v){
	if(tr[i].l>=l&&tr[i].r<=r){
		cov(i,v);return;
	}pushdown(i);
	int mid=tr[i].l+tr[i].r>>1;
	if(r<=mid) upd(ls,l,r,v);
	else if(l>mid) upd(rs,l,r,v);
	else upd(ls,l,mid,v),upd(rs,mid+1,r,v);
	pushup(i);
}
ll qmin(int i,int l,int r){
	if(tr[i].l>=l&&tr[i].r<=r)
		return tr[i].mn;
	pushdown(i);int mid=tr[i].l+tr[i].r>>1;
	if(r<=mid) return qmin(ls,l,r);
	else if(l>mid) return qmin(rs,l,r);
	else return min(qmin(ls,l,mid),qmin(rs,mid+1,r));
}//区间赋值区间最小线段树
ll lsh[MAXN];int cnt;
struct Query{
	int op;ll l,r,x;
	void input(){
		scanf("%d%lld%lld",&op,&l,&r);
		if(op==1) scanf("%lld",&x);
		lsh[++cnt]=l;lsh[++cnt]=r;
	}
}q[MAXN];//离散询问
ll tmp[MAXN];//暂存不含相邻点区间最小值的数组
int n,k,mp[MAXN];//mp[] 存 lsh 中的第 i 个位置对应到最终数组中的位置是 mp[i]
int MODp(int p){
	p=p%n;if(p==0) p=n;
	return p;
}//求位置 p 在一段循环节中是第几个
ll getmin(ll l,ll r){
	ll ret;
	if(r-l+1>=n) ret=tr[1].mn;
	else{
		l=MODp(l);r=MODp(r);
		if(l<=r) ret=qmin(1,l,r);
		else{
			ret=qmin(1,1,r);
			ret=min(ret,qmin(1,l,n));
		}
	}return ret;
}//求出原位置 [l,r] 的最小值
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
		scanf("%lld",&b[i]);
	build(1,1,n);//先对原序列建一下线段树
	int Q;
	scanf("%d",&Q);
	for(int i=1;i<=Q;i++){
		q[i].input();
	}sort(lsh+1,lsh+1+cnt);
	cnt=unique(lsh+1,lsh+1+cnt)-lsh-1;
	memset(tmp,0x3f,sizeof(tmp));
	for(int i=1;i<=Q;i++){
		int pl=lower_bound(lsh+1,lsh+1+cnt,q[i].l)-lsh;
		int pr=lower_bound(lsh+1,lsh+1+cnt,q[i].r)-lsh;
		tmp[pl]=b[MODp(q[i].l)];
		tmp[pr]=b[MODp(q[i].r)];
	}
	b[1]=tmp[1];mp[1]=1;
	int len=1;
	for(int i=1;i<cnt;i++){
		if(lsh[i]+1<=lsh[i+1]-1)
		b[++len]=getmin(lsh[i]+1,lsh[i+1]-1);//相邻两点之间的最小值
		b[++len]=tmp[i+1];mp[i+1]=len;//在询问中出现过的值
	}
	build(1,1,len);//对最终的序列建线段树
	for(int i=1;i<=Q;i++){
		int pl=lower_bound(lsh+1,lsh+1+cnt,q[i].l)-lsh;pl=mp[pl];
		int pr=lower_bound(lsh+1,lsh+1+cnt,q[i].r)-lsh;pr=mp[pr];
		//求出询问的两个端点在最终压缩后数组中的位置
		if(q[i].op==1) upd(1,pl,pr,q[i].x);
		else printf("%lld\n",qmin(1,pl,pr));
	}
}

End

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值