【LuoguP4927】梦美的线段树(LGR-053)-线段树

本文深入探讨了线段树算法的应用,特别是在处理区间更新和查询问题上的高效解决方案。通过一个具体的梦美的线段树测试案例,详细介绍了如何利用线段树进行平方和的维护与更新,包括向上合并操作及子树内叶子节点属性变化时的计算技巧。

测试地址:梦美的线段树
做法: 本题需要用到线段树。
经过简单的计算,题目所求的是:
a n s = ∑ s u m i 2 s u m 1 ans=\frac{\sum sum_i^2}{sum_1} ans=sum1sumi2
于是我们现在要求的就是 ∑ s u m i 2 \sum sum_i^2 sumi2
向上合并非常简单,主要难的是,当一棵子树内每个叶子节点都增加 x x x时,新的平方和如何计算。
s o n i son_i soni表示以点 i i i为根的子树内的叶子节点数,那么 s u m i sum_i sumi会变成 s u m i + s o n i ⋅ x sum_i+son_i\cdot x sumi+sonix,则:
( ∑ s u m i 2 ) n e w = ∑ ( s u m i + s o n i ⋅ x ) 2 (\sum sum_i^2)_{new}=\sum (sum_i+son_i\cdot x)^2 (sumi2)new=(sumi+sonix)2
= ∑ s u m i 2 + 2 x ∑ s u m i ⋅ s o n i + x 2 ⋅ ∑ s o n i 2 =\sum sum_i^2+2x\sum sum_i\cdot son_i+x^2\cdot \sum son_i^2 =sumi2+2xsumisoni+x2soni2
其中 ∑ s o n i 2 \sum son_i^2 soni2怎么都不会变,一开始处理好就行了,那么显然现在要处理 ∑ s u m i ⋅ s o n i \sum sum_i\cdot son_i sumisoni的维护,则:
( ∑ s u m i ⋅ s o n i ) n e w = ∑ ( s u m i + s o n i ⋅ x ) ⋅ s o n i (\sum sum_i\cdot son_i)_{new}=\sum (sum_i+son_i\cdot x)\cdot son_i (sumisoni)new=(sumi+sonix)soni
= ∑ s u m i ⋅ s o n i + x ∑ s o n i 2 =\sum sum_i\cdot son_i+x\sum son_i^2 =sumisoni+xsoni2
于是直接线段树维护即可,时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)
不过最后一个点比较毒瘤,要使用__int128之类的…然而我并不想影响我代码的美观性,所以感受一下感觉就行了…
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
int n,m;
ll a[100010],sum[400010],sumsum[400010];
ll tag[400010]={0},sumson[400010],sonson[400010];

ll power(ll a,ll b)
{
    ll s=1,ss=a;
    while(b)
    {
        if (b&1) s=s*ss%mod;
        ss=ss*ss%mod;
        b>>=1;
    }
    return s;
}

void pushup(int no,int l,int r)
{
    ll son=r-l+1;
    sum[no]=(sum[no<<1]+sum[no<<1|1])%mod;
    sumsum[no]=(sumsum[no<<1]+sumsum[no<<1|1]+sum[no]*sum[no]%mod)%mod;
    sumson[no]=(sumson[no<<1]+sumson[no<<1|1]+sum[no]*son%mod)%mod;
    sonson[no]=(sonson[no<<1]+sonson[no<<1|1]+son*son%mod)%mod;
}

void update(int no,int l,int r,ll x)
{
    ll son=r-l+1;
    tag[no]=(tag[no]+x)%mod;
    sum[no]=(sum[no]+son*x)%mod;
    sumsum[no]=(sumsum[no]+2ll*x*sumson[no]%mod+x*x%mod*sonson[no]%mod)%mod;
    sumson[no]=(sumson[no]+x*sonson[no]%mod)%mod;
}

void pushdown(int no,int l,int r)
{
    int mid=(l+r)>>1;
    if (tag[no])
    {
        update(no<<1,l,mid,tag[no]);
        update(no<<1|1,mid+1,r,tag[no]);
        tag[no]=0;
    }
}

void buildtree(int no,int l,int r)
{
    if (l==r)
    {
        sum[no]=a[l]%mod;
        sumsum[no]=a[l]*a[l]%mod;
        sumson[no]=a[l]%mod;
        sonson[no]=1ll;
        return;
    }
    int mid=(l+r)>>1;
    buildtree(no<<1,l,mid);
    buildtree(no<<1|1,mid+1,r);
    pushup(no,l,r);
}

void modify(int no,int l,int r,int s,int t,ll x)
{
    if (l>=s&&r<=t)
    {
        update(no,l,r,x);
        return;
    }
    int mid=(l+r)>>1;
    pushdown(no,l,r);
    if (s<=mid) modify(no<<1,l,mid,s,t,x);
    if (t>mid) modify(no<<1|1,mid+1,r,s,t,x);
    pushup(no,l,r);
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    
    buildtree(1,1,n);
    for(int i=1;i<=m;i++)
    {
        int op;
        scanf("%d",&op);
        if (op==1)
        {
            int l,r;
            ll x;
            scanf("%d%d%lld",&l,&r,&x);
            modify(1,1,n,l,r,x);
        }
        if (op==2) printf("%lld\n",sumsum[1]*power(sum[1],mod-2)%mod);
    }
    
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值