题目大意
给你一个序列 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));
}
}