Luogu P2122 【还教室】
-
P2122 还教室
这熟悉的题面与输入
三种操作:1个修改2种查询。
足以想到我们的经典数据结构——线段树。
没有完全掌握或者不会线段树的同学可以康康我的学习笔记然后A了模板再来看这题哦
这是一道基本可以说是模板形式的线段树题目,但是难度却不低。
无非是给定序列,让你维护区间平均值与方差。
但是这道题目离谱之处在于:
- 要求输出分数形式
不过如果了解了思路与做法后也就是多对通分约分操作进行了要求。
-
Prelude
这是我们机房模拟赛的题目。我就顺便说说我的思路历程吧。
其实最开始我的想法是线段树,但是我想得就是直接维护方差平均值(指线段树内就存方差和平均值,我是个狼灭)。
我是怎么想的呢,我建了一个子结构体,存储分数的分子分母。顺便写了分数加分数乘以及简化分数的函数。
我仔细分析复杂度与实现难度,炸了。
然后我脑中就开始思考方差和平均值的共性,拿出草稿纸计算,于是得出正解思路。如下。
-
Solution
做这题你需要一定的小学(或初中?)数学知识:
首先区间[l,r]
的平均值为(下文设为\(\bar a\)):
方差则是(下文设为\(s^2\)):
显然,平均值很好维护,我们只需要维护区间和再在输出时用分数表示区间和除以区间长度就行。
那么我们还要做的其实是用\(\bar a\)推出\(s^2\)。
我们展开单独的
分别分析\(a_i^2\),\(2a_i \bar a\),和 \(\bar a^2\)
- 对于\(\sum a_i^2\):
我们可以直接单独维护,并不复杂,但对于区间\(+x\)的操作我们可以推出懒惰标记的下传公式是:
在这里我们就很明显看出来我们要维护的就是区间和(\(\sum a_i\))与平方和(\(\sum a_i^2\))了。
但是根据上面式子,我们只需要维护一个懒惰标记。
- 对于\(\sum 2a_i \bar a\):
对于每个区间的平均值我们已经有了维护方法,所以在这我们将其视为定值(\(\frac{\sum a_i}{r-l+1}\)),于是我们将式子转换成:
- 对于\(\sum \bar a^2\)
显然我们有\(\sum \bar a^2\)=\((r-l+1)\bar a^2\)
我们再像之前一样代入\(\bar a\)
于是乎:
我们发现可以消了,于是列出全式:
可能略显麻烦,但是有结果就是好的。
所以我们得出了一个只需要\(\sum a_i^2\)和\(\sum a_i\)就可以维护区间方差的式子。
求方差时,最后结果就先查询区间和区间平方和,然后按照这个公式输出就可以了。
-
Code
有几个易错点在这里说一下。
-
记得开
long long
,虽然\(a_i\)和\(d\)的范围在两位数以内,但是一旦要求计算成一个\(10^5\)区间的平方和,这题没有模数,所以自然需要long long
。 -
下传标记和更新计算时先更新平方和再更新区间和,根据上面式子可知要的是更新前的区间和来更新平方和。在Pushdown函数中有体现(我的是
PD(int p)
)
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#define N 100005
#define ll long long
using namespace std;
struct Rey
{
int l,r;
ll s1,s2;
ll lazadd;
}T[N<<2];
int n,m;
ll a[N];
ll gcd(ll a,ll b)
{
return !b?a:gcd(b,a%b);
}
ll lcm(ll a,ll b)
{
return (a*b)/gcd(a,b);
}
int LS(int p){return p<<1;}
int RS(int p){return p<<1|1;}
void UPDATE_sum(int p)
{
T[p].s1=T[LS(p)].s1+T[RS(p)].s1;
T[p].s2=T[LS(p)].s2+T[RS(p)].s2;
}
void build(int p,int l,int r)
{
T[p].l=l,T[p].r=r;
if(l==r){T[p].s1=a[l],T[p].s2=a[l]*a[l];return;}
int mid=(l+r)>>1;
build(LS(p),l,mid);
build(RS(p),mid+1,r);
UPDATE_sum(p);
}
void PD(int p)
{
int L,R;
ll ADD=T[p].lazadd;
L=LS(p),R=RS(p);
T[L].s2+=2*ADD*T[L].s1+(T[L].r-T[L].l+1)*ADD*ADD*1ll;
T[R].s2+=2*ADD*T[R].s1+(T[R].r-T[R].l+1)*ADD*ADD*1ll;
T[L].s1+=ADD*(T[L].r-T[L].l+1)*1ll;
T[R].s1+=ADD*(T[R].r-T[R].l+1)*1ll;
T[L].lazadd+=ADD;
T[R].lazadd+=ADD;
T[p].lazadd=0;
}
void FIX(int p,int l,int r,ll x)
{
if(l>T[p].r||T[p].l>r)return;
if(T[p].l>=l&&T[p].r<=r)
{
T[p].lazadd+=x;
T[p].s2+=T[p].s1*2*x+(T[p].r-T[p].l+1)*x*x*1ll;
T[p].s1+=x*(T[p].r-T[p].l+1)*1ll;
return ;
}
if(T[p].lazadd)PD(p);
FIX(LS(p),l,r,x);
FIX(RS(p),l,r,x);
UPDATE_sum(p);
}
ll Vuq1(int p,int l,int r)//查询区间和
{
ll res=0;
if(l>T[p].r||T[p].l>r)return 0;
if(T[p].l>=l&&T[p].r<=r)return T[p].s1;
if(T[p].lazadd)PD(p);
res+=Vuq1(LS(p),l,r);
res+=Vuq1(RS(p),l,r);
return res;
}
ll Vuq2(int p,int l,int r)//区间平方和
{
ll res=0;
if(l>T[p].r||T[p].l>r)return 0;
if(T[p].l>=l&&T[p].r<=r)return T[p].s2;
if(T[p].lazadd)PD(p);
res+=Vuq2(LS(p),l,r);
res+=Vuq2(RS(p),l,r);
return res;
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
build(1,1,n);
while(m--)
{
int op;
scanf("%d",&op);
if(op==1)
{
int x,y;
ll z;
scanf("%d %d %lld",&x,&y,&z);
FIX(1,x,y,z);
}
if(op==2)
{
int x,y;
scanf("%d %d",&x,&y);
ll sum=Vuq1(1,x,y);
ll mu=y-x+1;
ll G=gcd(sum,mu);
printf("%lld/%lld\n",sum/G,mu/G);
}
if(op==3)
{
int x,y;
scanf("%d %d",&x,&y);
ll sum1=Vuq1(1,x,y);
ll sum2=Vuq2(1,x,y);
ll mu=(ll)(y-x+1);
ll sum=mu*sum2-sum1*sum1*1ll;
mu=mu*mu;
ll G=gcd(sum,mu);
printf("%lld/%lld\n",sum/G,mu/G);
//要求最简形式所以同除以GCD约分
}
}
return 0;
}