题解:P17738 [ICPC 2019 Xuzhou R] Yuuki and a problem
songzirui_ · · 题解
SOLUTION
看到这个题,首先我就想到了 [FJOI2016] 神秘数,这两个题除了带不带修完全一样。
先讲一下那个题的做法。
对于一个询问区间,我们从小到大考虑里面的每一个数,假设当前已经能把
设当前考虑到的数是
否则当前区间里不存在一个子集的和为
考虑优化这个过程。假如我们能一次把所有
具体地,设上一次扩展的答案区间为
在复杂度证明上,一次有效扩展至少会把答案区间扩展到
不带修的话,可以用主席树实现二维数点,容易做到
回到本题,显然可以直接上树套树飞过去,但是树套树的二维查询是
考虑优化不带修时的做法,使其更易维护。
将区间倍增地划分为
重现原迭代过程,设当前已经考虑完前
找到区间
否则加入
于是在一次扩展中,我们只需要求出在这个询问区间内,且在值域内的数的最小值与和,就能成功维护原迭代过程了。
这样就好做了,我们把值域划分为若干区间之后,就能把二维数点简化为区间查询了。
具体实现上,写
本题
CODE
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
int T,n,m;
const int V=200010,inf=0x3f3f3f3f3f3f3f3f;
int a[201000],lg[201000];
struct Data{
int x,y;
Data operator*(const Data &k)const{
return {x+k.x,min(y,k.y)};
}
};
struct segtree{
int sum[801000],Min[801000];
void init(){
for(int i=1;i<=n*4;i++) Min[i]=inf;
}
void pushup(int p){
sum[p]=sum[p<<1]+sum[p<<1|1];
Min[p]=min(Min[p<<1],Min[p<<1|1]);
}
void erase(int p,int L,int R,int pos){
if(L==R) return (void)(Min[p]=inf,sum[p]=0);
int mid=(L+R)>>1;
if(pos<=mid) erase(p<<1,L,mid,pos);
else erase(p<<1|1,mid+1,R,pos);
pushup(p);
}
void update(int p,int L,int R,int pos,int v){
if(L==R) return (void)(Min[p]=sum[p]=v);
int mid=(L+R)>>1;
if(pos<=mid) update(p<<1,L,mid,pos,v);
else update(p<<1|1,mid+1,R,pos,v);
pushup(p);
}
Data qry(int p,int L,int R,int l,int r){
if(l<=L&&R<=r) return {sum[p],Min[p]};
Data res={0,inf};
int mid=(L+R)>>1;
if(l<=mid) res=res*qry(p<<1,L,mid,l,r);
if(r>mid) res=res*qry(p<<1|1,mid+1,R,l,r);
return res;
}
}sgt[19];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=V;i++){
lg[i]=lg[i>>1]+1;
}
for(int i=1;i<=18;i++){
sgt[i].init();
}
for(int i=1;i<=n;i++){
cin>>a[i];
sgt[lg[a[i]]].update(1,1,n,i,a[i]);
}
while(m--){
int op,x,y;
cin>>op>>x>>y;
if(op==1){
sgt[lg[a[x]]].erase(1,1,n,x);
sgt[lg[y]].update(1,1,n,x,y);
a[x]=y;
}
else{
int now=0;
for(int i=1;i<=18;i++){
auto t=sgt[i].qry(1,1,n,x,y);
if(t.y>now+1) break;
now+=t.x;
}
cout<<now+1<<'\n';
}
}
return 0;
}