【Codeforces 878E】Numbers on the blackboard

贪心

题目大意:

给定一个数列,每个数是整数,你可以执行一个操作:选择一个位置(不能是最后一个位置),令这个位置和其后面的位置分别为 $x,y$,则将这两个位置删除并替换为 $x+2y$。

有多次询问,每次给定 $l,r$,表示对 $l,r$ 区间的数执行 $r-l$ 次操作,剩下的那个数最大是多少。

题解:

观察到,每个数对最终的贡献都是 $2^k$,其中 $k\geq 0$,并且 $\forall i\in[1,n-1]$,都有 $k_i\leq k_{i-1}+1$。

考虑使用贪心的策略,即令正数的系数尽可能大。

我们从左到右加入数,把贡献为正的部分放到一个块里,块 $[l,r]$ 内的每个位置 $i\in[l+1,r]$ 满足 $k_i=k_{i-1}+1$.

当遇到一个负数的时候,给它另外开一个块存。当遇到一个非负数,则往之前那个块合并。如果合并后导致原来一个贡献为负的块贡献变为正了,则继续往前合并,并且仍然保证 $k$ 依次递增的性质(显然这样分配的贡献是最大的)。

于是考虑将询问离线,并依次把每个数加入,用并查集维护一个数所在块(以块的最前端为根,顺便维护块长)。

考虑一段区间的贡献的时候,中间整块部分的维护一下前缀和即可。最左边的一个块可能不是完整的,但是由于 $k$ 递增的性质,所以可以像提取子串哈希值一样,预处理后缀,把这个部分的提取出来即可。

时间复杂度 $O(n\log n)$。

Code:

#include<iostream>
#include<vector>
using namespace std;
typedef long long LL;
const int N=5e5+7,md=1e9+7,inf=2e9+5;
int n,a[N],pr[N],_2[N],L[N],fa[N],f[N],ans[N],ps[N],s[N],m;
vector<pair<int,int> >vec[N];
inline void upd(int&a){a+=a>>31&md;}
void init(int n){for(int i=*_2=1;i<=n;++i)upd(_2[i]=(_2[i-1]<<1)-md);}
inline int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
void merge(int x,int y){
    fa[y]=x;
    if(L[x]>30&&s[x]>0||s[x]+((LL)s[y]<<L[x])>=inf)s[x]=inf;else s[x]+=s[y]<<L[x];
    f[x]=(f[x]+(LL)f[y]*_2[L[x]])%md;
    L[x]+=L[y];
}
inline int calc(int l,int r){return(pr[l]-(LL)pr[r+1]*_2[r-l+1]%md+md)%md;}
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>m;
    init(n);
    for(int i=1;i<=n;++i)
        cin>>a[i],upd(f[i]=s[i]=a[i]);
    for(int i=n;i;--i)
        pr[i]=((LL)(pr[i+1]<<1)+a[i]+md)%md;
    for(int i=1;i<=n;++i)fa[i]=i,L[i]=1;
    for(int i=1;i<=m;++i){
        int l,r;
        cin>>l>>r;
        vec[r].emplace_back(l,i);
    }
    for(int i=1;i<=n;++i){
        while(find(i)>1&&s[find(i)]>=0)merge(find(find(i)-1),find(i));
        upd(ps[find(i)]=ps[find(find(i)-1)]+f[find(i)]-md);
        for(auto q:vec[i]){
            int l=q.first,id=q.second;
            ans[id]=((LL)(ps[find(i)]-ps[find(l)]+md)*2+calc(l,find(l)+L[find(l)]-1)+md)%md;
        }
    }
    for(int i=1;i<=m;++i)
        cout<<ans[i]<<'\n';
    return 0;
}