数据结构专题 - 解题报告 - A

手推公式将其转化为:

n ∑ i = 1 n x 2 − ( ∑ i = 1 n x ) 2 n\sum_{i=1}^nx^2-(\sum_{i=1}^nx)^2 ni=1nx2(i=1nx)2

既然两边都变成了区间求和的形式,就可以用两棵线段树来维护,一个是x2 另一个是x的区间和。x的区间和修改很简单是板子操作,而x2 得好好谈谈了。
在每个点上乘上k并加上b的,使x2 变成 ( k*x + b )2,公式化就是

∑ i = 1 n ( k x + b ) 2 = k 2 ∑ i = 1 n x 2 + 2 k b ∑ i = 1 n x + b 2 \sum_{i=1}^n(kx+b)^2=k^2\sum_{i=1}^nx^2+2kb\sum_{i=1}^nx+b^2 i=1n(kx+b)2=k2i=1nx2+2kbi=1nx+b2

∑ i = 1 n x \sum_{i=1}^nx i=1nx又是已知的节点值(注意这里是修改前,这意味着要先修改平方和再修改x的和),所以我们在x2 上的乘法和加法需要修改的值也是可以算出来的。至于修改的先后顺序,先乘后加,这个推一下就出来了,实在不行就试一下就有了(我的有一步运算规则的顺序还真是试出来的),其他就真的只剩线段树基本操作了。
最后讲讲有三种修改方式,加、乘还有推平,我用了两个懒标记一个存加一个存乘法,推平不就是先全部乘0再加?

对了,这题好像很多人都卡了取模,反正嘛,就是往死里取,有加法乘法运算都要取模,还有减法取模的处理:先加模长再取模。我卡在了读入数据开始建树的时候a[ l ] * a[ l ] % qhqh,这里的取模是必不可少的,wa了几发,倒是找出错来了。
其实可以重载运算符’+‘和’×’,使计算后立即取模,会让代码看起来更舒服点。
定义qhqh为1e9+7,%%%%%%

AC代码如下:

#include<bits/stdc++.h>
#define maxn 100005
#define maxm 10000005
#define FOR(a, b, c) for(int a=b; a<=c; a++)
#define qhqh 1000000007
#define inf 2147483647
#define llinf 9223372036854775807
#define ll long long
#define pi acos(-1.0)
#define ls p<<1
#define rs p<<1|1
using namespace std;

ll l, r, n, m, d, a[maxn];
int type;
ll mul[maxn<<2], add[maxn<<2], tr[maxn<<2], tr2[maxn<<2];

//快读
inline ll read()
{
    char c=getchar();long long x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}

void pushup(ll p)
{
    tr[p] = (tr[ls] + tr[rs]) % qhqh;
    tr2[p] = (tr2[ls] + tr2[rs]) % qhqh;
}

void build(ll p, ll l, ll r)
{
    mul[p] = 1;
    if(l==r)        //左右相同,说明这是一个点(叶子节点)
    {
        tr[p] = a[l];      //给叶子点赋值键入的a[l]
        tr2[p] = a[l]*a[l]%qhqh;
        return;
    }
    ll mid = (l+r)>>1;
    build(ls, l, mid);
    build(rs, mid+1, r);
    pushup(p);           //这是在所有叶子节点赋值后才能进行的向上溯根并更新节点值的过程
}

void ADD(ll p, ll l, ll r, ll mulk, ll sumk)
{
    tr2[p] = tr2[p]*mulk%qhqh*mulk%qhqh;
    tr[p] = tr[p]*mulk%qhqh;
    tr2[p] =  ((tr2[p] + (r-l+1)*sumk%qhqh*sumk%qhqh)%qhqh + 2*sumk*tr[p]%qhqh) % qhqh;
    tr[p] += (r-l+1) * sumk % qhqh;
    tr[p] %= qhqh;
    add[p] = (add[p] * mulk % qhqh + sumk) % qhqh;
    mul[p] = (mul[p] * mulk) % qhqh;
}

void pushdown(ll p, ll l, ll r)
{
    ll mid = (l+r)>>1;
    ADD(ls, l, mid, mul[p], add[p]);
    ADD(rs, mid+1, r, mul[p], add[p]);
    mul[p] = 1;
    add[p] = 0;
}
//乘法更新
void update2(ll nl, ll nr, ll l, ll r, ll p, ll k)
{
    if(nl <= l && nr >= r)          //覆盖了递归当前节点全区的情况
    {
        ADD(p, l, r, k, 0);         //啊啊啊这个点暂时不想往下传了,先存起来
        return;
    }
    pushdown(p, l, r);
    ll mid = (l+r)>>1;
    if(nl <= mid)
        update2(nl, nr, l, mid, ls, k);
    if(nr >= mid+1)
        update2(nl, nr, mid+1, r, rs, k);
    pushup(p);
}
//加法更新
void update1(ll nl, ll nr, ll l, ll r, ll p, ll k)
{
    if(nl <= l && nr >= r)          //覆盖了递归当前节点全区的情况
    {
        ADD(p, l, r, 1, k);         //先存为敬,反正暂时没必要向下递归了
        return;
    }
    pushdown(p, l, r);
    ll mid = (l+r)>>1;
    if(nl <= mid)
        update1(nl, nr, l, mid, ls, k);
    if(nr >= mid+1)
        update1(nl, nr, mid+1, r, rs, k);
    pushup(p);
}
ll query1(ll nl, ll nr, ll l, ll r, ll p)        //原汁原味的查询
{
    ll ret = 0;
    if(nl <= l && nr>= r) return tr[p];
    ll mid = (l+r)>>1;
    pushdown(p, l, r);
    if(nl <= mid)
        ret += query1(nl, nr, l, mid, ls);
    if(nr >= mid+1)
        ret += query1(nl, nr, mid+1, r, rs);
    ret %= qhqh;
    return ret;
}
ll query2(ll nl, ll nr, ll l, ll r, ll p)        //原汁原味的查询
{
    ll ret = 0;
    if(nl <= l && nr>= r) return tr2[p];
    ll mid = (l+r)>>1;
    pushdown(p, l, r);
    if(nl <= mid)
        ret += query2(nl, nr, l, mid, ls);
    if(nr >= mid+1)
        ret += query2(nl, nr, mid+1, r, rs);
    ret %= qhqh;
    return ret;
}
int main()
{
    n = read();
    m = read();
    FOR(i, 1, n)
        a[i] = read();
    build(1, 1, n);         //从1开始建树
    FOR(i, 1, m)
    {
        type = read();
        if(type == 1)
        {
            l=read(); r=read(); d=read();
            update1(l, r, 1, n, 1, d);
            continue;
        }
        if(type == 2)
        {
            l=read(); r=read(); d=read();
            update2(l, r, 1, n, 1, d);
            continue;
        }
        if(type == 3)
        {
            l=read(); r=read(); d=read();
            update2(l, r, 1, n, 1, 0);
            update1(l, r, 1, n, 1, d);
        }
        if(type == 4)
        {
            l=read(); r=read();
            ll q1 = query1(l, r, 1, n, 1);
            ll q2 = query2(l, r, 1, n, 1);
            ll ans = ((r-l+1)*q2%qhqh - q1*q1%qhqh + qhqh) %qhqh;
            //cout<<"q1 = "<<q1<<endl<<"q2 = "<<q2<<endl;
            cout<<ans<<endl;
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值