A 掉这道题才发现自己是第 9 个 A 的,抓紧来发篇题解。
题目传送门
题意理解
一看到这道题,我发现其实就是 [KOI 2024 Round 1] 加倍 的加强版,多了一个区间查询。
题意:给你一个长度为 M 的正整数序列 X1,X2,…,XM,给你 l,r,每次操作可以把 Xi 乘 2,求出让 Xl,Xl+1,…,Xr 变为升序的最小操作次数。
解题思路
简化一下,对于 ∀i,Xi≤Xi+1×2ci,我们可以把每个 ci 存下来,如果 Xi 乘了 1 次,那么后面的 Xj 都会多乘 1 次,所以前面的会对后面的产生影响,那么就要计算前缀和,它们的前缀和加起来就是答案————————吗?
举个例子,序列 3,1,5,1,5 ,其对应的 ci 分别为 2,0,3,0,前缀和就是 2,2,5,5,结果为 14,很明显是不对的。
思考一下,发现需要计算每个 Xi 可以抵消掉后面的次数,那么这个 ci 就分别是 2,−2,3,−2,前缀和加起来就是 6,这才是正确的。
But,序列 1,10,5,ci 为 −3,1,但是前缀和加起来就是 −5,很明显的错误,所以如果前缀和变成了负数,要令其变为 0。
综上,我们可以归纳出,ci 是 Xi 和 Xi+1 之间的变化,前缀和 Si=max(0,Si−1+ci),对于区间 l,r,答案就是 ∑i=lrSi,如果每次跑一遍累加一下,复杂度是 O(MQ),只能得到 12pts。
考虑优化,将 ∑i=lrSi 拆开,得到 ∑i=lr∑j=licj,交换两个求和符号得 ∑j=lr∑i=jrcj,再化简一下是 ∑j=lr(r−j+1)cj,将 (r+1) 提出来得 (r+1)∑j=lrcj−∑j=lrj×cj,所以这就是答案的式子,可以用两个前缀和维护,就可以实现 O(1) 查询,但是由于 Si=max(0,Si−1+ci),设 Ri=Ri−1+ci,所以这个式子只能用于 Ri≥0 的区间。
对于 Ri<0 的情况,可以考虑分段,将连续的 Ri(Ri≥0) 分为一段,由于这一段 Ri≥0,所以就可以用 (r+1)∑j=lrcj−∑j=lrj×cj 算出每一段的答案,再拼成 l,r 的区间。
如何实现呢?我们知道,如果 Rj<Ri(j>i),那么这一段就是 <0 的,所以需要找到第一个比 Ri 小的 Rj,可以用单调栈维护,存入 nxti 表示第一个比 Ri 小的 Rj 的 j。
代码实现
复杂度大约是 O(Q),注意开 long long
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| #include<bits/stdc++.h> #define endl '\n' #define int long long using namespace std; const int N=3e5+10; int n,q,a[N],c[N],sum,cnt,s[N],ss[N],nxt[N]; stack<int> st; int getsum(int l,int r){ return (r+1)*(s[r]-s[l-1])-(ss[r]-ss[l-1]); } void zj(){ for(int i=n;i>=0;i--){ while(!st.empty()&&s[st.top()]>=s[i]) st.pop(); if(!st.empty()) nxt[i]=st.top(); else nxt[i]=n+1; st.push(i); } while(q--){ int l,r,ans=0; cin>>l>>r; if(l==r){ cout<<0<<endl; continue; } int i=l-1; while(i<r){ ans+=getsum(i+1,min(nxt[i]-1,r-1)); i=nxt[i]; } cout<<ans<<endl; } } signed main(){ cin.tie(0)->ios::sync_with_stdio(0); cin>>n>>q; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<n;i++){ if(a[i+1]>a[i]){ int x=a[i]; while(x*2<=a[i+1]){ x*=2; c[i]--; } }else{ int x=a[i+1]; while(x<a[i]){ x*=2; c[i]++; } } } for(int i=1;i<=n;i++) s[i]=s[i-1]+c[i]; for(int i=1;i<=n;i++) ss[i]=ss[i-1]+c[i]*i; zj(); return 0; }
|