纪念某模拟赛 8.6 k 代码
夏虫(summer)
题意简述:
用 \(n\) 个虫子,每个虫子有一个狡猾值 \(a_i\),一开始你会站在一个虫子 \(x\) 前,将初始能力值设为 \(a_x\),并捕捉它,接下来你可以重复执行三种操作,直到捕捉完所有昆虫:
设当前捕捉到了区间 \([l,r]\) 的昆虫,能力值为 \(v\)
- 若 \(l \ne 1\) 并且 \(a_{l−1} \le v\) ,那么可以花费 \(1\) 单位精力捕捉 \(l−1\) 号夏虫;
- 若 \(r \ne n\) 并且 \(a_{r+1} \le v\) ,那么她们可以花费 \(1\) 单位精力捕捉 \(r + 1\) 号夏虫;
- 花费 \(k\) (\(k\) 是定值)单位精力使得捕虫网的能力值为 \(min(a_{l−1}, a_{r+1})\)。在这里定义 \(a_0 = a_{n+1} = inf\)。
有 \(Q\) 次询问,每次给定 \(x,l,r\) 表示交换 \(x,x+1\) 两只昆虫的位置后,询问你初始站在 \([l,r]\) 内的每只昆虫前的最小花费精力的和。注意交换操作是永久的,即会对后面的询问产生影响。
题解:
首先操作 \(1\) 和操作 \(2\) 肯定加起来要做 \(n-1\) 次,所以只需要看操作 \(3\) 要几次。
容易用单调栈维护处 \(L[i]/R[i]\) 表示 \(a[i]\) 作为最大值的区间。
那么设 \(f[i]\) 表示以 \(i\) 为开始需要多少次操作 \(3\),不妨设 \(a[L[i]-1] < a[R[i]+1]\),则 \(f[i]=f[L[i]-1] + 1\)。
所以按照值域从大到小 \(dp\) 即可做到 \(O(n)\) 求解单次询问,那么时间复杂度 \(O(nQ)\),有 40 分。
但是如果按照这种转移的求法,不是很好在每次修改后维护 \(f\),考虑换一个求法:
设 \(A_x\) 表示 \(a[1],a[2],...,a[x]\) 的后缀最大值的集合。\(B_x\) 表示 \(a[x],a[x+1],...,a[n]\) 的前缀最大值的集合。
那么答案就是 \(|A_x \cup B_x| - 1\),-1 是因为 \(a[x]\) 自己是不用算的,这个式子应该还好理解,就不证了。
我们维护两棵线段树 \(Seg1,Seg2\)。\(Seg1\) 维护 \(a\) 序列,\(Seg2\) 维护 \(f\) 。
注意:我们是无法去维护 \(A,B\) 集合的,即不能对每个数开两个 \(multiset\),当然因为无法维护,我们也不需要真的在集合里删掉某些数(这是好的)
假设现在交换了 \(a[x],a[x+1]\),去考虑会影响哪些数的 \(A,B\) 集合。
- \(a[x]<a[x+1]\)
- 对于 \(1 \le i \le x-1\) 的 \(i\),它们的 \(A\) 集合显然是不变的,但是 \(B\) 集合里有可能原来有 \(a[x]\),现在就没了(因为 \(a[x+1]\) 跑到 \(a[x]\) 前面了)。
首先我们需要知道哪些数的 \(B\) 集合原来有 \(a[x]\),容易知道一定是 \([l,x-1]\) 这一段区间的数,其中每个数都 \(<a[x]\),注意如果 \(=a[x]\) 是不算的,因为 \(=a[x]\) 的话他的 \(B\) 集合里仍然有 \(a[x]\)。
求这个 \(l\) 在 \(Seg1\) 上线段树二分即可,但是要知道 \(i \in [l,x-1]\) 的 \(f[i]\) 是否改变还要看 \(A[i]\) 里是否有 \(a[x]\),这里不用也不能去每个数判断一遍,注意到对于 \(A[i]\),他在 \(a[l],a[l+1],..,a[i]\) 的后缀\(max\)里肯定是没有 \(a[x]\) 的,所以只需要去看 \(a[l-1]\) 是否 \(=a[x]\) 即可。
如果 \(a[l-1] \ne a[x]\)的话他们的 \(f[i]-1\),即在 \(Seg2\) 上区间 \(-1\)。 - 对于 \(i\ge x+2\),他们的 \(B\) 集合不变,\(A\) 集合要改变的是一段区间 \([x+2,r]\),他们都 \(<a[x]\),它们的 \(A\) 集合里会多出 \(a[x]\)。
同理求 \(r\) 只需要线段树二分。判断 \(f\) 是否要 \(+1\) 只需要看 \(a[r+1]\) 是否 \(=a[x]\)。 - 对于 \(i=x,i=x+1\),注意到这个时候我已经求出了其他 \(i\) 的 \(f\) 值,所以直接按照最初的 \(f[i]=f[(L[i]-1) / (R[i]+1)] + 1\) 转移即可,\(L[i],R[i]\) 也可线段树二分求。注意要按照从大到小的顺序 DP
- \(a[x]>a[x+1]\) ,类似分类讨论,不想写了。
用上面的写法是 \(O(n + Qlogn)\) 但是因为不知道怎么写这个线段树二分,所以这边想到了有两种做法:
- 来自大佬 @RiceFruit 的: 在 \([l,r]\) 中查询第一个满足 \(max(a[i],a[i+1],..,a[r]) \le val\) 的位置 \(i\),就先把 \([l,r]\) 拆成 \(log\) 个区间,然后先从后往前扫一遍判断在哪一个子树内,这样对于一整个子树的求解是好做的。
- 暴力二分用线段树查询的做法,所以是双 \(log\) 的。
一看就方法\(2\)好写,所以一开始写 \(O(Q \times log^2n)\),但是这样要跑 \(1.6\)s,当时看着自己TLE的 \(8k\) 代码人都炸了。
所以还是写第一种吧。
代码有亿点点长,个人码风问题,但真的感觉写出来也是挺不容易的。
Swap函数那里码风突变是因为不加空格看着太恶心了。
code
#include<bits/stdc++.h>
#define PII pair<int,int>
#define fi first
#define se second
#define int long long
using namespace std;
const int N=1e5+5,inf=1e18;
inline int read(){
int w = 1, s = 0;
char c = getchar();
for (; c < '0' || c > '9'; w *= (c == '-') ? -1 : 1, c = getchar());
for (; c >= '0' && c <= '9'; s = 10 * s + (c - '0'), c = getchar());
return s * w;
}
int n,k,T,a[N];
PII t[N];
int L[N],R[N],f[N];
stack<int> st;
struct node1{
int l,r,maxn;
};
struct SegmentTree1{
node1 t[N<<2];
vector<int> v; //存区间用
void pushup(int p){
if(t[p<<1].maxn>t[p<<1|1].maxn) t[p].maxn=t[p<<1].maxn;
else t[p].maxn=t[p<<1|1].maxn;
}
void build(int p,int l,int r){
t[p].l=l,t[p].r=r;
if(l==r){
t[p].maxn=a[l];
return;
}
int mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
pushup(p);
}
int ask(int p,int l,int r){
if(l<=t[p].l&&t[p].r<=r){
return t[p].maxn;
}
int mid=(t[p].l+t[p].r)>>1,maxn=0;
if(l<=mid) maxn=max(maxn,ask(p<<1,l,r));
if(r>mid) maxn=max(maxn,ask(p<<1|1,l,r));
return maxn;
}
void Get_range(int p,int l,int r){
if(l<=t[p].l&&t[p].r<=r){
v.push_back(p);
return;
}
int mid=(t[p].l+t[p].r)>>1;
if(l<=mid) Get_range(p<<1,l,r);
if(r>mid) Get_range(p<<1|1,l,r);
}
int lower1(int p,int val,int op){
if(t[p].l==t[p].r){
if( (op==0&&t[p].maxn<val) || (op==1&&t[p].maxn<=val) ) return t[p].l;
else return n+1;
}
if( (op==0&&t[p<<1|1].maxn<val) || (op==1&&t[p<<1|1].maxn<=val) ) return min(lower1(p<<1,val,op) , t[p<<1|1].l);
else return lower1(p<<1|1,val,op);
}
int lower2(int p,int val,int op){
if(t[p].l==t[p].r){
if( (op==0&&t[p].maxn<val) || (op==1&&t[p].maxn<=val) ) return t[p].l;
else return 0;
}
if( (op==0&&t[p<<1].maxn<val) || (op==1&&t[p<<1].maxn<=val) ) return max(t[p<<1].r , lower2(p<<1|1,val,op));
else return lower2(p<<1,val,op);
}
int ask1(int l,int r,int val,int op){ //在 l~r 中查询第一个满足 max(a[i],a[i+1],..,a[r]) </<= val 的位置
if(l>r) return n+1;
if( (op==0&&a[r]>=val) || (op==1&&a[r]>val) ) return n+1; //先保证有解
v.clear(); //子树下标放这个里面,从左往右放
Get_range(1,l,r);
int ans=r;
for(int i=v.size()-1;i>=0;i--){
int p=v[i];
if( (op==0&&t[p].maxn<val) || (op==1&&t[p].maxn<=val) ) ans=min(ans,t[p].l); //这里只需要比当前子树的最大值就好了,扫过的那些已经满足了
else{
ans=min(ans,lower1(p,val,op));
break;
}
}
return ans;
}
int ask2(int l,int r,int val,bool op){ //在 l~r 中查询最后一个满足 max(a[l],a[l+1],..,a[i]) </<= val 的位置
if(l>r) return 0;
if( (op==0&&a[l]>=val) || (op==1&&a[l]>val) ) return 0;
v.clear();
Get_range(1,l,r);
int ans=l;
for(int i=0;i<v.size();i++){
int p=v[i];
if( (op==0&&t[p].maxn<val) || (op==1&&t[p].maxn<=val) ) ans=max(ans,t[p].r);
else{
ans=max(ans,lower2(p,val,op));
break;
}
}
return ans;
}
void change(int p,int x,int val){
if(t[p].l==t[p].r){
t[p].maxn=val;
return;
}
int mid=(t[p].l+t[p].r)>>1;
if(x<=mid) change(p<<1,x,val);
else change(p<<1|1,x,val);
pushup(p);
}
}Seg1;
struct node2{
int l,r,sum,add;
void tag(int d){
sum+=(r-l+1ll)*d,add+=d;
}
};
struct SegmentTree2{
node2 t[N<<2];
void pushup(int p){
t[p].sum=t[p<<1].sum+t[p<<1|1].sum;
}
void spread(int p){
if(t[p].add){
t[p<<1].tag(t[p].add);
t[p<<1|1].tag(t[p].add);
t[p].add=0;
}
}
void build(int p,int l,int r){
t[p].l=l,t[p].r=r;
if(l==r){
t[p].sum=f[l];
return;
}
int mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
pushup(p);
}
void change(int p,int x,int val){
if(t[p].l==t[p].r){
t[p].sum=val;
return;
}
spread(p);
int mid=(t[p].l+t[p].r)>>1;
if(x<=mid) change(p<<1,x,val);
else change(p<<1|1,x,val);
pushup(p);
}
void modify(int p,int l,int r,int d){
if(l<=t[p].l&&t[p].r<=r){
t[p].tag(d);
return;
}
spread(p);
int mid=(t[p].l+t[p].r)>>1;
if(l<=mid) modify(p<<1,l,r,d);
if(r>mid) modify(p<<1|1,l,r,d);
pushup(p);
}
int ask(int p,int l,int r){
if(l<=t[p].l&&t[p].r<=r){
return t[p].sum;
}
spread(p);
int mid=(t[p].l+t[p].r)>>1,res=0;
if(l<=mid) res+=ask(p<<1,l,r);
if(r>mid) res+=ask(p<<1|1,l,r);
return res;
}
int ask2(int p,int x){
if(t[p].l==t[p].r) return t[p].sum;
spread(p);
int mid=(t[p].l+t[p].r)>>1;
if(x<=mid) return ask2(p<<1,x);
else return ask2(p<<1|1,x);
}
}Seg2;
void Init(){
st.push(0);
for(int i=1;i<=n;i++){
while(st.size()&&a[st.top()]<=a[i]) st.pop();
L[i]=st.top()+1ll;
st.push(i);
}
while(st.size()) st.pop();
st.push(n+1);
for(int i=n;i>=1;i--){
while(st.size()&&a[st.top()]<=a[i]) st.pop();
R[i]=st.top()-1ll;
st.push(i);
}
f[0]=f[n+1]=-1ll;
sort(t+1,t+n+1,[](PII x,PII y){return x.fi>y.fi;});
for(int i=1;i<=n;i++){
if(a[L[t[i].se]-1] < a[R[t[i].se]+1]) f[t[i].se]=f[L[t[i].se]-1]+1ll;
else f[t[i].se]=f[R[t[i].se]+1]+1ll;
}
Seg1.build(1,0,n+1);
Seg2.build(1,0,n+1); //虽然查不到 0/n+1 但是可能要用
}
void Swap(int x){
if(a[x]==a[x+1]) return;
if(a[x]<a[x+1]){
int l = Seg1.ask1(1 , x-1 , a[x] , 0);
if(l<=x-1 && a[l-1] != a[x]) Seg2.modify(1 , l , x-1 , -1ll);
int r=Seg1.ask2(x+2 , n , a[x] , 0);
if(r>=x+2 && a[r+1] != a[x]) Seg2.modify(1 , x+2 , r , 1ll);
Seg1.change(1 , x , a[x+1]) , Seg1.change(1 , x+1 , a[x]);
int maxn=a[x+1],ming=a[x];
swap(a[x],a[x+1]);
//先更新大的再更新小的
int L1 = Seg1.ask1(1 , x , maxn , 1) , R1 = Seg1.ask2(x , n , maxn , 1) , F; //注意这个时候 a[x+1] 在 x 的位置
if( a[L1-1] < a[R1+1] ) F = Seg2.ask2(1 , L1-1) + 1ll;
else F = Seg2.ask2(1 , R1+1) + 1ll;
Seg2.change(1 , x , F);
L1 = Seg1.ask1(1 , x+1 , ming , 1) , R1 = Seg1.ask2(x+1 , n , ming , 1) , F; //注意这个时候 a[x] 在 x+1 的位置
if( a[L1-1] < a[R1+1] ) F = Seg2.ask2(1 , L1-1) + 1ll;
else F = Seg2.ask2(1,R1+1) + 1ll;
Seg2.change(1 , x+1 , F);
}
else{ //a[x]>a[x+1]
int l = Seg1.ask1(1 , x-1 , a[x+1] , 0); //此时可能多出来一个 a[x+1]
if(l<=x-1 && a[l-1] != a[x+1]) Seg2.modify(1 , l , x-1 , 1ll);
int r = Seg1.ask2(x+2 , n , a[x+1] , 0);
if(r>=x+2 && a[r+1] != a[x+1]) Seg2.modify(1 , x+2 , r , -1ll);
Seg1.change(1 , x ,a[x+1]) , Seg1.change(1 , x+1 , a[x]);
int maxn=a[x],ming=a[x+1];
swap(a[x],a[x+1]);
int L1 = Seg1.ask1(1 , x+1 , maxn , 1) , R1 = Seg1.ask2(x+1 , n , maxn , 1) , F;
if( a[L1-1] < a[R1+1] ) F = Seg2.ask2(1 , L1-1) + 1ll;
else F = Seg2.ask2(1 , R1+1) + 1ll;
Seg2.change(1 , x+1 , F);
L1 = Seg1.ask1(1 , x , ming , 1) , R1 = Seg1.ask2(x , n , ming , 1) , F;
if( a[L1-1] < a[R1+1] ) F = Seg2.ask2(1 , L1-1) + 1ll;
else F = Seg2.ask2(1 , R1+1) + 1ll;
Seg2.change(1 , x , F);
}
}
signed main(){
freopen("summer.in","r",stdin);
freopen("summer.out","w",stdout);
n=read(),k=read();
for(int i=1;i<=n;i++) a[i]=read(),t[i].fi=a[i],t[i].se=i;
a[0]=a[n+1]=inf;
Init();
T=read();
while(T--){
int x=read(),l=read(),r=read();
Swap(x);
printf("%lld\n",Seg2.ask(1,l,r)*k + (n-1)*(r-l+1));
}
return 0;
}