<题目连接>node
<big>ui
Capella 和 Frank 同样爱好天文学。spa
她常在冬季的夜晚,如有所思地望着东北方上空的五边形中,最为耀眼的一个顶点。code
那一抹金黄曾带给她多少美好的遐想!get
直到有一天,她作了这一题,并饱受线段树的折磨。string
</big>it
<big>io
这是一道区间操做的题目,解法有线段树与分块等。class
题主选择线段树进行讲解(由于没用分块写这题)。gc
</big>
<big>
首先要知道,线段树须要维护的数值有哪些。
先来看题目中所给的公式。
$\bar x = \frac 1 {r-l+1} \sum_{i=l}^r x_i$
$\bar y = \frac 1 {r-l+1} \sum_{i=l}^r y_i$
$\hat a = \frac{\sum_{i=l}^r (x_i - \bar x)(y_i - \bar y)}{\sum_{i=l}^r (x_i - \bar x)^2}$
将线性回归方程的公式展开得($\sum_{i=l}^r$ 省略为 $\sum$):
$\hat a = \frac{\sum (x_i y_i - \bar x y_i - \bar y x_i + \bar x \bar y)} {\sum (x_i^2 - 2\bar x x_i + \bar x^2)}$
$= \frac{\sum x_i y_i - \bar x \sum y_i - \bar y \sum x_i + (r-l+1) \bar x \bar y} {\sum x_i^2 - 2\bar x \sum x_i + (r-l+1) \bar x^2}$
将 $\bar x$、$\bar 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}}$。
</big>
<big>
进行区间加操做时,设 $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$ 的值,因此应注意顺序。
</big>
$1^2 + 2^2 + \dots + n^2 = \frac {n(n+1)(2n+1)} 6$
<big>
试图将区间修改操做转化为区间加操做。
对于 $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
(不管区间曾经加了多少都没有用,将被统一修改)。
</big>
<big>
清空Lazy Tag
后,对区间 $[l,r]$ 进行区间加操做,即:每一个 $x_i$ 加上 $S$,每一个 $y_i$ 加上 $T$。
更新过程与区间加操做彻底相同。
最后还要打上标记c
,表示此区间的子区间须要区间修改操做。
</big>
<big>
必定记得下传Lazy Tag
与c
标记,我就是由于没传才调了一上午+一下午+半个晚上的。
本题下传标记部分较为复杂,请必定注意顺序。
</big>
<big>
这道题有点像《HAOI2012 高速公路》啊。
线段树这种东西,多推一推也就熟悉啦。
加油,各位队友;加油,本身。
</big>
#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\n",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; }
谢谢阅读。