CF1817D Half-sum

前言

前几天 @adamant 在 CF 上发表文章介绍了 Anton Trygub 发明的维护大数的算法 The Trygub Numbers

简要地讲,这个算法支持维护一个 \(n\)\(b\) 进制数:

  • 给定 \(i,v\),将数加上 \(vb^i\)
  • 给定 \(i\),查询数的第 \(i\) 位的值。
  • 查询数的正负。

其主要思想是将平常每位只能存 \([0,b)\) 的限制扩宽成能够存 \((-b,b)\) 内的任意整数。

这样,数的正负仍可以通过删除前导零后第一个数的正负表示;数的第 \(i\) 位的值只需要 lower_bound 后如果第 \(i\) 位有值那么看看其下一个(更低)非零位是正是负决定是否 \(-1\);修改只需正常进位(进位的增量可以为负数),可以证明压位后均摊线性。(压位需要满足压位后的进制数 \(b'\approx\) 每次加的数的值域)

对于时间限制较为宽松的题目,可以考虑使用 map 维护。
对于时间限制较紧并且不需要查询数的第 \(i\) 位的值的题目,还是用数组维护比较好。具体方法依题而定,以下题解中给出了一种参考实现。

题解

首先不难想到题目等价于求所有 \(i\in [1,n-1]\) 的下列式子的最大值:

\[(2^{n-i-1}a_{i+1}+\sum_{j>i+1}2^{n-j+1}a_j)-(2^{-(i-1)}a_i+\sum_{j<i}2^{-j}a_j) \]

暴力算是 \(O(n^2)\) 的,long double 精度不够,所以只能够用一个高位二进制数来做,需要支持比较大小。

题解给出了一种巧妙的分治做法来优化时间复杂度。

考虑到比较 \(i,j(i<j)\) 的对应式子值的大小实际上是两个高位二进制数作差,而 \(<i-1,>j+1\) 的部分都消掉了,所以实际上只与 \([i-1,j+1]\) 的值有关。

这种“局部性”提示了分治。具体来说,我们每次将 \([l,r]\) 分为 \([l,mid]\)\([mid+1,r]\),分别递归地求出其局部的最大值取到时分界点 \(i\) 的位置——\(L\)\(R\)。这时只需要把 \([L-1,R+1]\) 对应上式的项的值作个差,判断得到的数的正负,来决定返回 \(L\) 还是 \(R\) 作为 \([l,r]\) 的局部最优解。

不用 map 怎么实现查找最高位呢?由于每次预备作差时都会清空这个 Trygub Number,所以我们可以在作差的过程中记录下所有被修改的位,最后把这些位分别取出来,找到现在非零的位中最高的位即可。

时间复杂度 \(O(n\log n)\)(均摊)。注意压位后才均摊正确!(不压位跑 2s+,压位后 300ms-)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
namespace IO {
const int buflen=1<<21;
char ch,buf[buflen],*p1=buf,*p2=buf;
int x,f;
inline char gc(){
    return p1==p2&&(p2=buf+fread(p1=buf,1,buflen,stdin),p1==p2)?EOF:*p1++;
}
inline int read(){
    x=0,f=1;ch=gc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=0;ch=gc();}
    while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=gc();
    return f?x:-x;
}
}
using IO::read;
const int N=1e6+5,mod=1e9+7,I2=(mod+1)/2;
int n,a[N];
ll res[N];
struct anton {
    ll bin[N+122],*mp=bin+60;
	int bin2[N+122],*bk=bin2+60,vis[N+122],tot;
    inline void add(int x,ll y){
        mp[x]+=y;if(!bk[x])vis[++tot]=x,bk[x]=1;
        ll t;
        do {
            if(mp[x]<0)t=(-mp[x])>>30,t=-t;
            else t=mp[x]>>30;
            mp[x]-=t<<30;
            mp[x-1]+=t;
            if(!bk[x-1])vis[++tot]=x-1,bk[x-1]=1;
            x--;
        }while(t);
    }
    inline void Add(int x,int y){
    	add(((x+29)/30),(ll)y*(1ll<<((x+29)/30*30-x)));
	}
    inline int sig(){
        int mn=1e9;
        for(int i=1;i<=tot;i++){
            if(mp[vis[i]])mn=min(mn,vis[i]);
        }
        return mn==1e9?0:mp[mn]>0?1:-1;
    }
    inline void clr(){
        for(int i=1;i<=tot;i++)mp[vis[i]]=bk[vis[i]]=0;tot=0;
    }
}haha;
int solve(int l,int r){//returns the best position k
    if(l==r)return l;
    int mid=l+r>>1;
    int L=solve(l,mid);
    int R=solve(mid+1,r);
    haha.clr();
    for(int i=L-1;i<=R+1;i++)haha.Add(i<=R?i-(i==R):n-i+1-(i==R+1),i<=R?-a[i]:a[i]),haha.Add(i<=L?i-(i==L):n-i+1-(i==L+1),i<=L?a[i]:-a[i]);
    return haha.sig()>=0?R:L;
}
int main(){
    int T;
    cin>>T;
    while(T--){
        n=read();
        for(int i=1;i<=n;i++)a[i]=read();
        sort(a+1,a+n+1);
        int pos=solve(1,n-1);
        for(int i=0;i<=n;i++)res[i]=0;
        for(int i=1;i<pos;i++)res[i]-=a[i];
        res[pos-1]-=a[pos];
        for(int i=pos+2;i<=n;i++)res[n-i+1]+=a[i];
        res[n-pos-1]+=a[pos+1];
        int ans=0;
        for(int i=0,I=1;i<=n;i++,I=(ll)I*I2%mod)ans=(ans+(ll)I*(res[i]+mod))%mod;
        cout<<ans<<'\n';
    }
}

//y*2^{-x}  =  y *  (2^-30)^{ceil(x/30)} +                       2^{-x}- 2^{-ceil(x/30)*30}
posted @ 2023-05-10 22:47  pengyule  阅读(36)  评论(0编辑  收藏  举报