【CF878E】Numbers on the blackboard(贪心)
- 给定一个长度为 \(n\) 的序列 \(a_{1\sim n}\)。
- \(q\) 次询问,每次取出序列中的一个区间 \([l,r]\) 内的元素进行操作。
- 一次操作可以合并相邻两个元素(假设依次为 \(x\) 和 \(y\)),得到一个值为 \(x+2y\) 的新元素。对于每次询问,求出求最终能得到的元素的最大值。
- \(1\le n,q\le10^5\),\(|a_i|\le10^9\),答案向 \(10^9+7\) 取模
巧妙的贪心策略
设第 \(i\) 个元素对答案的贡献系数为 \(2^{k_i}\)。
除开头元素外,第 \(i\) 个元素都至少被合并一次,且一次合并之后就会始终跟随着第 \(i-1\) 个元素,也就是说 \(1\le k_i\le k_{i-1}+1\)。
而我们肯定想让正数的系数尽可能大,因此如果一个数是正数,我们肯定贪心地让 \(k_i\) 取满 \(k_{i-1}+1\)。
考虑把 \(k_i\) 连续递增的一段元素称作一个块。
则正数肯定会被添到上一个块的末尾,负数则会自成一个新块。
但要,如果正数被添到上一个块末尾后上一个块的权值变成了正数,我们需要继续把这个块看成一个元素向前合并。具体实现可以用一个栈来维护所有的块,要保证除第一个块之外的所有块权值为负。
注意用于判断正负的权值是不能取模的,而如果无脑维护可能会爆 long long。实际上,当权值非常大时,肯定能一路向前合并完,可以统一设为 INF。
离线询问
询问可以离线,按右端点 \(r\) 排序,这样我们就可以维护好加入 \(1\sim r\) 中所有元素的局面,然后每次 lower_bound 找出第 \(l\) 个元素所在的块(假设为 \(x\))。
\(l\) 到所在块结尾 \(ed\) 中第 \(i\) 个元素贡献系数为 \(2^{i-l}\),只需用 \(\sum_{i=l}^{ed}a_i\times 2^i\) 除以 \(2^l\),这可以预处理一个前缀和差分求解。
而第 \(x+1\) 个块开始的答案就是这些块的权值和,同样维护前 \(i\) 个块的权值和每次差分一下即可。
注意除开头元素外,每个元素至少合并一次,\(k_i\ge 1\)。
代码:\(O(n\log n)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 100000
#define X 1000000007
#define I2 500000004
#define LL long long
#define INF (LL)1e18
using namespace std;
int n,a[N+5],ans[N+5];struct Q {int p,l,r;I bool operator < (Cn Q& o) Cn {return r<o.r;}}q[N+5];
int pw[N+5],ipw[N+5],S[N+5],V[N+5],St[N+5],F[N+5];LL G[N+5];
namespace FastIO
{
#define FS 100000
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
int ff,OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
Tp I void read(Ty& x) {x=0,ff=1;W(!isdigit(oc=tc())) ff=oc^'-'?1:-1;W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));x*=ff;}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('\n');}
}using namespace FastIO;
int main()
{
RI Qt,i;for(read(n,Qt),i=1;i<=n;++i) read(a[i]);for(i=1;i<=Qt;++i) read(q[i].l,q[i].r),q[i].p=i;
for(pw[0]=ipw[0]=i=1;i<=n;++i) pw[i]=2LL*pw[i-1]%X,ipw[i]=1LL*I2*ipw[i-1]%X,S[i]=(S[i-1]+1LL*pw[i]*(a[i]+X))%X;//预处理
RI x,j=1,T=0;for(sort(q+1,q+Qt+1),i=1;i<=n;++i)//将询问离线,枚举右端点
{
St[++T]=i,F[T]=(a[i]+X)%X,G[T]=a[i];//新开一个块
W(T>1&&G[T]>=0) x=St[T]-St[T-1],F[T-1]=(F[T-1]+1LL*F[T]*pw[x])%X,G[T-1]=G[T-1]+1.0*G[T]*pw[x]>=INF?INF:G[T-1]+G[T]*pw[x],--T;//若最后一个块权值非负,向前合并
V[T]=(V[T-1]+F[T])%X,St[T+1]=i+1;//V记录前i块元素之和
W(j<=Qt&&q[j].r==i) x=upper_bound(St+1,St+T+1,q[j].l)-St,ans[q[j].p]=(1LL*ipw[q[j].l]*(S[St[x]-1]-S[q[j].l-1]+X)+2LL*(V[T]-V[x-1]+X))%X,++j;//找到l所在块求解
}
for(i=1;i<=Qt;++i) writeln(ans[i]);return clear(),0;
}
待到再迷茫时回头望,所有脚印会发出光芒