2021牛客暑期多校训练营7 K-xay loves sequence(主席树+二分)

这篇博客探讨了如何利用差分数组解决区间修改问题,特别是在模数限制下的优化策略。通过引入辅助变量和差分,将原数组的区间±1操作转化为对差分数组的选择性修改,以最小化某个目标函数。文章详细介绍了如何使用主席树进行区间查询和修改,以及在询问过程中如何选取合适的修改策略来提高效率。最后,通过一个具体的代码实现展示了这种思想的应用。

K-xay loves sequence

首先不考虑模 k k k的限制,容易想到对原数组做一个差分得到 d i = a i − a i − 1 d_i=a_i-a_{i-1} di=aiai1,显然对于 ∀ 1 ≤ i ≤ n a i = 0 \forall_{1\leq i\leq n} a_i=0 1inai=0 等价于 ∀ 1 ≤ i ≤ n d i = 0 \forall_{1\leq i\leq n} d_i=0 1indi=0,而对于原数组的区间 ± 1 \pm1 ±1等价于在差分数组 d d d中找到两个数一个数+1另一个数-1,或者说找到一个位置 pos \text {pos} pos单独+1或者-1(这意味着原数组 [ a pos → a n ] ± 1 [a_{\text{pos}}\to a_n] \pm1 [aposan]±1)。上面的最优解显然是 max ⁡ { ∑ d + , ∣ ∑ d − ∣ } \max\{\sum d_+,|\sum{d_-}|\} max{d+,d}

上述变换有些麻烦,如果我们引入 a n + 1 = 0 a_{n+1}=0 an+1=0,并且引入 d n + 1 = a n + 1 − a n d_{n+1}=a_{n+1}-a_n dn+1=an+1an后,对于原数组进行区间 ± 1 \pm1 ±1等价于在差分数组 d 1 → n + 1 d_{1\to n+1} d1n+1选两个数一个+1另一个-1。并且不难发现无论怎么进行上述操作总有 ∑ i = 1 n + 1 d i = 0 \sum_{i=1}^{n+1} d_i=0 i=1n+1di=0最优解 max ⁡ { ∑ d + , ∣ ∑ d − ∣ } = ∑ i = 1 n + 1 ∣ d i ∣ 2 \max\{\sum d_+,|\sum{d_-}|\}=\frac{\sum_{i=1}^{n+1} |d_i|}{2} max{d+,d}=2i=1n+1di


hipamp题解

考虑模 k k k的意义下意味着我们可以花费0的代价使得某些 a i ± k a_i\pm k ai±k,对应在差分数组上即是 d i ± k , d i + 1 ∓ k d_i\pm k,d_{i+1}\mp k di±k,di+1k,换句话说现在可以让成对的 d i ± k , d j ∓ k d_i\pm k,d_j\mp k di±k,djk最终结果仍然是让 ∀ 1 ≤ i ≤ n d i + 1 = 0 \forall_{1\leq i\leq n} d_{i+1}=0 1indi+1=0

不难发现 d i = d i + k d_i=d_i+k di=di+k那么一定有 d i < 0 d_i<0 di<0,同理 d i = d i − k d_i=d_i-k di=dik那么一定有 d i ≥ 0 d_i\ge0 di0

如果 d i = d i + k d_i=d_i+k di=di+k,那么贡献从 ∣ d i ∣ 2 → ∣ d i + k ∣ 2 , Δ = k − 2 ∣ d i ∣ 2 \frac{|d_i|}{2}\to\frac{|d_i+k|}{2},\Delta=\frac{k-2|d_i|}{2} 2di2di+k,Δ=2k2di

如果 d i = d i − k d_i=d_i-k di=dik,那么贡献从 ∣ d i ∣ 2 → ∣ d i − k ∣ 2 , Δ = k − 2 ∣ d i ∣ 2 \frac{|d_i|}{2}\to\frac{|d_i-k|}{2},\Delta=\frac{k-2|d_i|}{2} 2di2dik,Δ=2k2di

于是只需要把 d i d_i di分为小于0的一组和大于0的一组,按照 Δ = k − 2 ∣ d i ∣ 2 \Delta=\frac{k-2|d_i|}{2} Δ=2k2di从小到大排序,每次取两数组中开头的两个 ( Δ + ) + ( Δ − ) (\Delta_+)+(\Delta_-) (Δ+)+(Δ),如果能使答案变小即 ( Δ + ) + ( Δ − ) < 0 (\Delta_+)+(\Delta_-)<0 (Δ+)+(Δ)<0即一直取。


首先如果没有区间 l , r l,r l,r的限制,每次询问只给一个 k k k的话可以二分取了多少对 d − + k , d + − k d_{-}+k,d_{+}-k d+k,d+k,快速的求出答案。显然加上区间限制只需要套一个主席树即可。

注意对于 [ l , r ] [l,r] [l,r]区间来说差分数组是 ( a l − 0 ) , d l + 1 , … d r , ( 0 − a r ) (a_l-0),d_{l+1},\dots d_{r},(0-a_r) (al0),dl+1,dr,(0ar),也就是主席树维护 d i d_i di,然后每次询问会增添两个数 ( a l − 0 ) (a_l-0) (al0) ( 0 − a r ) (0-a_r) (0ar),一个插在正主席树,一个在负主席树。一个显然的想法是单点修改2次,然后询问过后删除,就如下面注释的代码。但是主席树不支持修改,因为修改不仅仅影响一棵主席树,而是会影响许多棵树,于是只需要在询问的时候带上这个数即可。

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
template <class T=int> T rd()
{
    T res=0;T fg=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') fg=-1;ch=getchar();}
    while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
    return res*fg;
}
const int N=200010;
const int U=(1ll<<31)-1;
int a[N],d[N];
ll s[N];
int n,m;
struct node
{
    int l,r;
    ll v,ct;
}tree[N*100];
int rt[2][N],cnt;
void update(int l,int r,int pre,int &u,int v,int c)
{
    u=++cnt;
    tree[u]=tree[pre];
    tree[u].ct+=c;
    tree[u].v+=v*c;
    if(l==r) return;
    int mid=l+r>>1;
    if(v<=mid) 
        update(l,mid,tree[pre].l,tree[u].l,v,c);
    else
        update(mid+1,r,tree[pre].r,tree[u].r,v,c);
}
// void change(int &u,int l,int r,int v,int c)
// {
//     if(!u) u=++cnt;
//     tree[u].ct+=c;
//     tree[u].v+=v*c;
//     if(l==r) return;
//     int mid=l+r>>1;
//     if(v<=mid) 
//         change(tree[u].l,l,mid,v,c);
//     else
//         change(tree[u].r,mid+1,r,v,c);
// }
// // 求前k大的sum
// ll query(int l,int r,int L,int R,int k)
// {
//     if(!k) return 0ll;
//     if(l==r) return 1ll*k*l;
//     int mid=l+r>>1;
//     int tmp=tree[tree[R].r].ct-tree[tree[L].r].ct;
//     if(k<=tmp)  
//         return query(mid+1,r,tree[L].r,tree[R].r,k);
//     else 
//         return (1ll*tree[tree[R].r].v-tree[tree[L].r].v)+query(l,mid,tree[L].l,tree[R].l,k-tmp);
// }
// // 计算数组l~r的答案 取前k大
// ll calc(int l,int r,int k,int x)
// {
//     // a[l]+d[l+1]~d[r]+abs(0-a[r])
    
//     ll v=(s[r]-s[l]+a[r]+a[l])/2;
//     ll s1=query(0,U,rt[0][l],rt[0][r],k);
//     ll s2=query(0,U,rt[1][l],rt[1][r],k);
//     return v-s1-s2+1ll*k*x;
// }
// 求前k大的sum
ll query(int l,int r,int L,int R,int k,int v)
{
    if(!k) return 0ll;
    if(l==r) return 1ll*k*l;
    int mid=l+r>>1;
    int tmp=tree[tree[R].r].ct-tree[tree[L].r].ct+(mid<v&&v<=r);
    if(k<=tmp)  
        return query(mid+1,r,tree[L].r,tree[R].r,k,v);
    else 
        return (1ll*tree[tree[R].r].v-tree[tree[L].r].v)+(mid<v&&v<=r)*v+query(l,mid,tree[L].l,tree[R].l,k-tmp,v);
}
// 计算数组l~r的答案 取前k大
ll calc(int l,int r,int k,int x)
{
    // a[l]+d[l+1]~d[r]+abs(0-a[r])
    
    ll v=(s[r]-s[l]+a[r]+a[l])/2;
    ll s1=query(0,U,rt[0][l],rt[0][r],k,a[l]);
    ll s2=query(0,U,rt[1][l],rt[1][r],k,a[r]);
    return v-s1-s2+1ll*k*x;
}
int main()
{
    n=rd(),m=rd();
    for(int i=1;i<=n;i++) 
    {
        a[i]=rd();
        d[i]=abs(a[i]-a[i-1]);
        s[i]=s[i-1]+d[i];
        if(a[i]>=a[i-1]) 
        {
            update(0,U,rt[0][i-1],rt[0][i],d[i],1);
            rt[1][i]=rt[1][i-1];
        }
        else
        {
            update(0,U,rt[1][i-1],rt[1][i],d[i],1);
            rt[0][i]=rt[0][i-1];
        }
    }
    while(m--)
    {
        int x=rd(),y=rd(),k=rd();
        
        
        // a[x] d[x+1] d[x+2]... d[y] abs(0-a[y])
        // change(rt[0][y],0,U,a[x],+1);
        // change(rt[1][y],0,U,a[y],+1);

        int l=0,r=min(tree[rt[0][y]].ct-tree[rt[0][x]].ct,tree[rt[1][y]].ct-tree[rt[1][x]].ct)+1;
        //cout<<l<<' '<<r<<'\n';
        while(l<r)
        {
            int mid=l+r>>1;
            if(calc(x,y,mid,k)<=calc(x,y,mid+1,k)) r=mid;
            else l=mid+1;
        }
        
        printf("%lld\n",calc(x,y,l,k));

        // change(rt[0][y],0,U,a[x],-1);
        // change(rt[1][y],0,U,a[y],-1);
    }
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值