• [Luogu 3707] SDOI2017 相关分析


    [Luogu 3707] SDOI2017 相关分析

    <题目链接>


    前言

    Capella 和 Frank 一样爱好天文学。

    她常在冬季的夜晚,若有所思地望着东北方上空的五边形中,最为耀眼的一个顶点。

    那一抹金黄曾带给她多少美好的遐想!

    直到有一天,她做了这一题,并饱受线段树的折磨。

    概述

    这是一道区间操作的题目,解法有线段树与分块等。

    题主选择线段树进行讲解(因为没用分块写这题)。

    准备与计算答案

    首先要知道,线段树需要维护的数值有哪些。

    先来看题目中所给的公式。

    (ar x = frac 1 {r-l+1} sum_{i=l}^r x_i)

    (ar y = frac 1 {r-l+1} sum_{i=l}^r y_i)

    (hat a = frac{sum_{i=l}^r (x_i - ar x)(y_i - ar y)}{sum_{i=l}^r (x_i - ar x)^2})

    将线性回归方程的公式展开得((sum_{i=l}^r) 省略为 (sum)):

    (hat a = frac{sum (x_i y_i - ar x y_i - ar y x_i + ar x ar y)} {sum (x_i^2 - 2ar x x_i + ar x^2)})

    (= frac{sum x_i y_i - ar x sum y_i - ar y sum x_i + (r-l+1) ar x ar y} {sum x_i^2 - 2ar x sum x_i + (r-l+1) ar x^2})

    (ar x)(ar y) 代入上式得:

    (hat a = frac{sum x_i y_i - frac{sum x_i sum y_i}{r-l+1} - frac{sum x_i sum y_i}{r-l+1} + (r-l+1) frac{sum x_i}{r-l+1} cdot frac{sum y_i}{r-l+1}} {sum x_i^2 - frac{2(sum x_i)^2}{r-l+1} + (r-l+1) (frac{sum x_i}{r-l+1})^2})

    (= frac{sum x_i y_i - frac{sum x_i sum y_i}{r-l+1}} {sum x_i^2 - frac{(sum x_i)^2}{r-l+1}})

    由此可见,线段树需要维护的数值有 (sum x_i^2)(sum x_i y_i)(sum x_i) 以及 (sum y_i)

    (v_0 = sum x_i^2, v_1 = sum x_i y_i, v_2 = sum x_i, v_3 = sum y_i)

    则最终计算出的 (hat a = frac{v_1 - frac{v_2 v_3}{r-l+1}}{v_0 - frac{v_2^2}{r-l+1}})

    区间加操作

    进行区间加操作时,设 (x) 的变化量为 (S)(y) 的变化量为 (T),则各个标记的下传过程如下:

    (sum (x_i + S)^2 = sum (x_i^2 + 2Sx_i + S^2))
    (= sum x_i^2 + 2S sum x_i + (r-l+1)S^2)

    (sum (x_i+S)(y_i+T) = sum (x_i y_i + Sy_i+Tx_i+ST))
    (=sum x_i y_i + S sum y_i + T sum x_i + (r-l+1)ST)

    (sum (x_i+S) = sum x_i + (r-l+1)S)

    (sum (y_i+T) = sum y_i +(r-l+1)T)

    特别注意:由于更新 $sum x_i^2 $ 与 (sum x_i y_i) 时,需要用到更新前的 (sum x_i)(sum y_i) 的值,所以应注意顺序

    区间修改操作

    前置技能

    (1^2 + 2^2 + dots + n^2 = frac {n(n+1)(2n+1)} 6)

    转化

    试图将区间修改操作转化为区间加操作。

    对于 (i in [l,r])(x_i) 改为 (S+i)(y_i) 改为 (T+i)

    相当于将 (i in [l,r]) 的每个 (x_i)(y_i) 都改为 (i)

    (sum x_i^2 = sum y_i^2 = sum i^2 = frac {r(r+1)(2r+1)} 6 - frac {l(l-1)(2l-1)} 6)(即 (sum_{i=1}^r i^2 -sum_{i=1}^{l-1} i^2)

    (sum x_i = sum y_i = sum i = frac {(r-l+1)(l+r)} 2)(r-l+1) 为区间元素个数,(frac {l+r} 2) 为区间平均值)

    然后清空Lazy Tag(无论区间曾经加了多少都没有用,将被统一修改)。

    维护操作

    清空Lazy Tag后,对区间 ([l,r]) 进行区间加操作,即:每个 (x_i) 加上 (S),每个 (y_i) 加上 (T)

    更新过程与区间加操作完全相同。

    最后还要打上标记c,表示此区间的子区间需要区间修改操作。

    注意事项

    • 一定记得下传Lazy Tagc标记,我就是因为没传才调了一上午+一下午+半个晚上的。

    • 本题下传标记部分较为复杂,请一定注意顺序

    结束语

    这道题有点像《HAOI2012 高速公路》啊。

    线段树这种东西,多推一推也就熟悉啦。

    加油,各位队友;加油,自己。

    #include <cstdio>
    #include <cstring>
    const int MAXN=100010;
    double x[MAXN],y[MAXN];
    int n,m;
    class SegmentTree
    {
        public:
            void Build(int i,int l,int r)
            {
                s[i]=node(l,r);
                if(l==r)
                {
                    s[i].v[0]=x[l]*x[l],s[i].v[1]=x[l]*y[r],s[i].v[2]=x[l],s[i].v[3]=y[r];
                    return;
                }
                int j=i<<1,mid=l+r>>1;
                Build(j,l,mid),Build(j|1,mid+1,r),PushUp(i);
            }
            void Add(int i,int l,int r,double S,double T)
            {
                if(l==s[i].l && r==s[i].r)
                {
                    AddModify(i,S,T);
                    return;
                }
                if(s[i].l^s[i].r)
                    PushDown(i);
                int j=i<<1,mid=s[i].l+s[i].r>>1;
                if(r<=mid)
                    Add(j,l,r,S,T);
                else if(l>mid)
                    Add(j|1,l,r,S,T);
                else
                    Add(j,l,mid,S,T),Add(j|1,mid+1,r,S,T);
                PushUp(i);
            }
            void Change(int i,int l,int r,double S,double T)
            {
                if(l==s[i].l && r==s[i].r)
                {
                    ChangeModify(i),AddModify(i,S,T);
                    return;
                }
                if(s[i].l^s[i].r)
                    PushDown(i);
                int j=i<<1,mid=s[i].l+s[i].r>>1;
                if(r<=mid)
                    Change(j,l,r,S,T);
                else if(l>mid)
                    Change(j|1,l,r,S,T);
                else
                    Change(j,l,mid,S,T),Change(j|1,mid+1,r,S,T);
                PushUp(i);
            }
            double Ans(double l,double r)
            {
                double size=r-l+1;
                memset(ans,0,sizeof ans);
                Sum(1,l,r);
                return (ans[1]-ans[2]*ans[3]/size)/(ans[0]-ans[2]*ans[2]/size);
            }
        private:
            double ans[4];
            struct node
            {
                bool c;
                double S,T,v[4];
                int l,r;
                node(int _l=0,int _r=0)
                {
                    l=_l,r=_r,c=S=T=0;
                }
            }s[MAXN<<2];
            double Calc(double i)
            {
                return i*(i+1)*(2*i+1)/6;
            }
            void AddModify(int i,double S,double T)
            {
                double size=double(s[i].r-s[i].l+1);
                s[i].v[0]+=S*S*size+2*S*s[i].v[2];
                s[i].v[1]+=S*T*size+S*s[i].v[3]+T*s[i].v[2];
                s[i].v[2]+=S*size;
                s[i].v[3]+=T*size;
                s[i].S+=S,s[i].T+=T;
            }
            void ChangeModify(int i)
            {
                double l=double(s[i].l),r=double(s[i].r);
                s[i].v[0]=s[i].v[1]=Calc(r)-Calc(l-1),s[i].v[2]=s[i].v[3]=(r-l+1)*(l+r)/2,s[i].c=1,s[i].S=s[i].T=0;
            }
            void PushUp(int i)
            {
                int l=i<<1,r=l|1;
                for(int k=0;k<4;++k)
                    s[i].v[k]=s[l].v[k]+s[r].v[k];
            }
            void PushDown(int i)
            {
                int l=i<<1,r=l|1;
                if(s[i].c)
                    ChangeModify(l),ChangeModify(r);
                AddModify(l,s[i].S,s[i].T),AddModify(r,s[i].S,s[i].T);
                s[i].c=s[i].S=s[i].T=0;
            }
            void Sum(int i,int l,int r)
            {
                if(l==s[i].l && r==s[i].r)
                {
                    for(int k=0;k<4;++k)
                        ans[k]+=s[i].v[k];
                    return;
                }
                if(s[i].l^s[i].r)
                    PushDown(i);
                int j=i<<1,mid=s[i].l+s[i].r>>1;
                if(r<=mid)
                    Sum(j,l,r);
                else if(l>mid)
                    Sum(j|1,l,r);
                else
                    Sum(j,l,mid),Sum(j|1,mid+1,r);
            }
    }SgT;
    int main(int argc,char *argv[])
    {
        scanf("%d %d",&n,&m);
        for(int i=1;i<=n;++i)
            scanf("%lf",&x[i]);
        for(int i=1;i<=n;++i)
            scanf("%lf",&y[i]);
        SgT.Build(1,1,n);
        for(int i=1,opt,l,r;i<=m;++i)
        {
            double S,T;
            scanf("%d %d %d",&opt,&l,&r);
            switch(opt)
            {
                case 1:
                    printf("%.10lf
    ",SgT.Ans(l,r));
                    break;
                case 2:
                    scanf("%lf %lf",&S,&T);
                    SgT.Add(1,l,r,S,T);
                    break;
                case 3:
                    scanf("%lf %lf",&S,&T);
                    SgT.Change(1,l,r,S,T);
                    break;
            }
        }
        return 0;
    }
    

    谢谢阅读。

  • 相关阅读:
    设置VS2017背景图片
    NuGet的简单使用
    C#6.0,C#7.0新特性
    openFileDialog的Filter属性设置
    C# 获取当前路径7种方法
    正则表达式总结
    IDEA设置switch/case代码块自动补齐
    CentOS7使用yum安装RabbitMQ
    vue react 路由history模式刷新404问题解决方案
    @Component, @Repository, @Service的区别
  • 原文地址:https://www.cnblogs.com/Capella/p/8481720.html
Copyright © 2020-2023  润新知