【学习笔记】CF1817E Half-sum

首先最优的贪心策略为:将原数组 排序 后,选择一个分界点 p p p,然后对于 [ 1 , p ] [1,p] [1,p]从后往前合并, [ p + 1 , n ] [p+1,n] [p+1,n]从前往后合并,将前后两个数的差值作为答案。模拟二进制下的加法即可做到 O ( n ) O(n) O(n)检查。注意这里不需要均摊维护高精度加法(这样做会多一个 log ⁡ \log log),因为我们不关心中途的结果。

然后,从一次复合函数的角度推导(这样形式更为简洁):

f ( a 1 , . . . , a i , x ) = a x + b f(a_1,...,a_i,x)=ax+b f(a1,...,ai,x)=ax+b,那么 f ( a 1 , . . . , a i , a i + 1 , x ) = a ⋅ a i + 1 + x 2 + b = 1 2 a x + 1 2 a ⋅ a i + 1 + b f(a_1,...,a_i,a_{i+1},x)=a\cdot \frac{a_{i+1}+x}{2}+b=\frac{1}{2}ax+\frac{1}{2}a\cdot a_{i+1}+b f(a1,...,ai,ai+1,x)=a2ai+1+x+b=21ax+21aai+1+b

注意到 a = 1 2 i a=\frac{1}{2^{i}} a=2i1,因此从增长率的角度来看,应该只选前 30 30 30项和后 30 30 30项进行检查。

通常,我们可以通过邻项比较(找 变化量)的方法来排除掉一些一定不优的方案。

发现 f ( a 1 , . . . , a i + 1 ) − f ( a 1 , . . . , a i ) = 1 2 a ⋅ ( a i + 1 − a i ) = 1 2 i ( a i + 1 − a i ) f(a_1,...,a_{i+1})-f(a_1,...,a_i)=\frac{1}{2}a\cdot(a_{i+1}-a_i)=\frac{1}{2^i}(a_{i+1}-a_i) f(a1,...,ai+1)f(a1,...,ai)=21a(ai+1ai)=2i1(ai+1ai),因此考虑原数组的差分数组 d i = a i + 1 − a i d_i=a_{i+1}-a_i di=ai+1ai,那么从方案 i i i j j j的变化量为: − d i 2 i − ∑ i < k < j d k ( 1 2 k − 1 2 n − k ) + d j 2 n − j -\frac{d_i}{2^i}-\sum_{i2idii<k<jdk(2k12nk1)+2njdj

注意到,如果 d i = 0 d_i=0 di=0那么 i i i一定不会成为最优分界点,因此我们 应该满足 d i > 0 d_i>0 di>0 。那么,当 i ≤ j ≤ n 2 i\le j\le \frac{n}{2} ij2n时,中间的式子一定 ≤ 0 \le 0 0,只有当 n − j ≤ i + 30 n-j\le i+30 nji+30时才可能成为答案。即: j ≥ n − i − 30 j\ge n-i-30 jni30。这样,我们只要找到第一个满足 ≤ n 2 − 30 \le \frac{n}{2}-30 2n30并且 b i > 0 b_i>0 bi>0的位置,这一定是最优的;如果不存在,那么检验 [ n 2 − 30 , n 2 ] [\frac{n}{2}-30,\frac{n}{2}] [2n30,2n]中的所有位置。其等价实现是,检验前 30 30 30个满足 b i > 0 b_i>0 bi>0的位置。对于 j > n 2 j>\frac{n}{2} j>2n的情况显然是同理的。

复杂度 O ( n log ⁡ V ) O(n\log V) O(nlogV)

#include
#define ll long long
#define pb push_back
#define fi first
#define se second
#define db double
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
const int mod=1e9+7;
const int N=1e6+65;
int T,n,m,res;
ll a[N],inv2=(mod+1)/2;
ll b[N],c[N],r[N];
void solve(int x){
    for(int i=0;i<=n+60;i++)b[i]=c[i]=0;
    for(int i=x;i>=1;i--){
        int j=i-(i==x);
        b[n-j]+=a[i];
    }for(int i=0;i<=n+60;i++)b[i+1]+=b[i]/2,b[i]%=2;
    for(int i=x+1;i<=n;i++){
        int j=n-i+1-(i==x+1);
        c[n-j]+=a[i];
    }for(int i=0;i<=n+60;i++)c[i+1]+=c[i]/2,c[i]%=2;
    for(int i=0;i<=n+60;i++){
        c[i]-=b[i];if(c[i]<0)c[i]+=2,c[i+1]--;
    }
    int it=n+60;while(it&&c[it]==r[it])it--;
    if(c[it]>r[it]){
        for(int i=0;i<=n+60;i++)r[i]=c[i];
        res=x;
    }
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>T;
    while(T--){
        cin>>n;for(int i=1;i<=n;i++)cin>>a[i];
        sort(a+1,a+1+n);res=1;for(int i=0;i<=n+60;i++)r[i]=0;
        int j=1;
        for(int i=1;i<=30;i++){
            while(j<n&&a[j]==a[j+1])j++;
            if(j==n)break;solve(j),j++;
        }j=n;
        for(int i=1;i<=30;i++){
            while(j>1&&a[j]==a[j-1])j--;
            if(j==1)break;solve(j-1),j--;
        }ll x=a[res];
        for(int i=res-1;i>=1;i--)x=(x+a[i])*inv2%mod;
        ll y=a[res+1];
        for(int i=res+2;i<=n;i++)y=(y+a[i])*inv2%mod;
        cout<<(y-x+mod)%mod<<"\n";
    }
}

你可能感兴趣的:(学习,笔记,算法)