[BZOJ4127]Abs(树链剖分)

=== ===

这里放传送门

=== ===

题解

这题不知道是ATP写麻烦了还是它本来就这么麻烦。。一开始看到这题的时候以为挺简单的:正数负数分开维护不就好了嘛!然后开心地写啊写写到pushdown的时候发现woc维护区间和的话就要知道正数和负数分别的增量,也就是要知道这个区间内有多少个正数多少个负数。。那也就要知道每次操作以后有多少负数变成了正数。然而这个题很重要的一点提示就是增量d一定是正数,那么每次只会有负数不停地变成正数,并且每个数最多变化一次。那么只要维护负数的最大值和它出现的位置,每次暴力修改就可以了。注意修改的顺序,应该先修改那些原来就是正数的,再查出由负数变成正数的,再更改剩下的负数,这样才不会出现重复标记的问题。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define inf 2000000000
using namespace std;
int n,m,v[100010],w[100010],num[100010],deep[100010],top[100010],f[500010],size[100010];
int p[100010],a[200010],next[200010],tot,cnt,fa[100010],son[100010],Maxpos[500010];
long long ndlt[500010],pdlt[500010],pos[500010],neg[500010],Max[500010];
void add(int x,int y){
    tot++;a[tot]=y;next[tot]=p[x];p[x]=tot;
}
long long abs(long long x){return (x>0)?x:-x;}
void dfs(int u){
    deep[u]=deep[fa[u]]+1;
    size[u]=1;son[u]=0;
    for (int i=p[u];i!=0;i=next[i])
      if (a[i]!=fa[u]){
          int v=a[i];
          fa[v]=u;dfs(v);
          size[u]+=size[v];
          if (size[v]>size[son[u]]) son[u]=v;
      }
}
void dfs_again(int u,int tp){
    top[u]=tp;w[u]=++cnt;num[cnt]=v[u];
    if (son[u]!=0) dfs_again(son[u],tp);
    for (int i=p[u];i!=0;i=next[i])
      if (a[i]!=fa[u]&&a[i]!=son[u])
        dfs_again(a[i],a[i]);
}
void update(int i){
    pos[i]=pos[i<<1]+pos[(i<<1)+1];
    neg[i]=neg[i<<1]+neg[(i<<1)+1];
    if (Max[i<<1]>Max[(i<<1)+1]){
        Max[i]=Max[i<<1];Maxpos[i]=Maxpos[i<<1];
    }//以Max为基础更新Maxpos,注意当数值相同的时候位置靠后的优先
    else{
        Max[i]=Max[(i<<1)+1];Maxpos[i]=Maxpos[(i<<1)+1];
    }
    f[i]=f[i<<1]+f[(i<<1)+1];
}
void build(int i,int l,int r){
    if (l==r){
        Maxpos[i]=l;
        if (num[l]<0){//正数和负数分开处理
          neg[i]=Max[i]=num[l];f[i]=1;}
        else {
          pos[i]=num[l];num[l]=-inf;Max[i]=num[l];}
        return;
    }
    int mid=(l+r)>>1;
    build(i<<1,l,mid);
    build((i<<1)+1,mid+1,r);
    update(i);
}
void Padd(int i,int l,int r,long long v){
    long long T=r-l+1;
    if (f[i]==T) return;//如果这个区间里没有正数就返回
    pdlt[i]+=v;
    pos[i]+=v*(T-f[i]);
}
void Nadd(int i,int l,int r,long long v){
    if (neg[i]==0) return;//如果没有负数就返回
    ndlt[i]+=v;neg[i]+=v*f[i];Max[i]+=v;
}
void pushdown(int i,int l,int r){
    if (pdlt[i]!=0){
        int mid=(l+r)>>1;
        Padd(i<<1,l,mid,pdlt[i]);
        Padd((i<<1)+1,mid+1,r,pdlt[i]);
        pdlt[i]=0;
    }
    if (ndlt[i]!=0){
        int mid=(l+r)>>1;
        Nadd(i<<1,l,mid,ndlt[i]);
        Nadd((i<<1)+1,mid+1,r,ndlt[i]);
        ndlt[i]=0;
    }
}
void maxchange(int i,int l,int r,int x,int v){
    if (l==r){
        pos[i]+=v;neg[i]=0;//v代表这个负数加成正数以后的数值
        num[l]=Max[i]=-inf;//消除的这个负数不能让它再成为最大值
        f[i]--;return;
    }
    int mid=(l+r)>>1;
    pushdown(i,l,r);
    if (x<=mid) maxchange(i<<1,l,mid,x,v);
    else maxchange((i<<1)+1,mid+1,r,x,v);
    update(i);
}
long long askmax(int i,int l,int r,int left,int right,long long &X){
    if (left<=l&&right>=r){
        X=max(X,Max[i]);//顺便维护最大值
        return Maxpos[i];
    }
    int mid=(l+r)>>1;
    long long lc,rc;
    pushdown(i,l,r);lc=rc=0;
    if (left<=mid) lc=askmax(i<<1,l,mid,left,right,X);
    if (right>mid) rc=askmax((i<<1)+1,mid+1,r,left,right,X);
    if (lc==0) return rc;if (rc==0) return lc;
    if (Max[i<<1]>Max[(i<<1)+1]) return lc;//从左右子树的答案里选择一个更优的
    return rc;
}
void Tadd(int i,int l,int r,long long v){
    long long Pos,wer=-inf;
    Padd(i,l,r,v);//先更改那些原本就是正数的
    Pos=askmax(1,1,n,l,r,wer);//查询最大负数
    while (wer!=-inf&&wer+v>=0){
        maxchange(1,1,n,Pos,wer+v);
        wer=-inf;Pos=askmax(1,1,n,l,r,wer);
    }
    Nadd(i,l,r,v);//更改剩下的负数
}
void add(int i,int l,int r,int left,int right,int d){
    if (left<=l&&right>=r){
        Tadd(i,l,r,d);return;
    }
    int mid=(l+r)>>1;
    pushdown(i,l,r);
    if (left<=mid) add(i<<1,l,mid,left,right,d);
    if (right>mid) add((i<<1)+1,mid+1,r,left,right,d);
    update(i);
}
long long askval(int i,int l,int r,int left,int right){
    if (left<=l&&right>=r) return pos[i]-neg[i];
    int mid=(l+r)>>1;
    long long ans=0;
    pushdown(i,l,r);
    if (left<=mid) ans+=askval(i<<1,l,mid,left,right);
    if (right>mid) ans+=askval((i<<1)+1,mid+1,r,left,right);
    return ans;
}
void change(int x,int y,int d){
    while (top[x]!=top[y]){
        if (deep[top[x]]<deep[top[y]]) swap(x,y);
        add(1,1,n,w[top[x]],w[x],d);
        x=fa[top[x]];
    }
    if (deep[x]>deep[y]) swap(x,y);
    add(1,1,n,w[x],w[y],d);
}
long long query(int x,int y){
    long long ans=0;
    while (top[x]!=top[y]){
        if (deep[top[x]]<deep[top[y]]) swap(x,y);
        ans+=askval(1,1,n,w[top[x]],w[x]);
        x=fa[top[x]];
    }
    if (deep[x]>deep[y]) swap(x,y);
    ans+=askval(1,1,n,w[x],w[y]);
    return ans;
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d",&v[i]);
    for (int i=1;i<n;i++){
        int u,v;scanf("%d%d",&u,&v);
        add(u,v);add(v,u);
    }
    dfs(1);dfs_again(1,1);
    build(1,1,n);
    for (int i=1;i<=m;i++){
        int k;scanf("%d",&k);
        if (k==1){
            int u,v,d;
            scanf("%d%d%d",&u,&v,&d);
            change(u,v,d);
        }else{
            int u,v;
            scanf("%d%d",&u,&v);
            printf("%I64d\n",query(u,v));
        }
    }
    return 0;
}

看到这里的人真是感谢你能受得了上面那一坨又丑又长的东西〒▽〒。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值