HGOI 202311月 && xoj [4816] 还教室(classroom)题解

 

题面

【题目描述】

还记得 NOIP 2012 提高组 Day2 中的借教室吗?时光飞逝,光阴荏苒,两年过去了,曾经借教室的同学们纷纷归还自己当初租借的教室。请你来解决类似于借教室的另一个问题。

在接受借教室请求的 n 天中,第 i 天剩余的教室为 ai 个。作为大学借教室服务的负责人,你需要完成如下三种操作共 m 次:

• 第 l 天到第 r 天,每天被归还 d 个教室

• 询问第 l 天到第 r 天教室个数的平均数

• 询问第 l 天到第 r 天教室个数的方差

【输入格式】

第一行包括两个正整数 n 和 m,其中 n 为借教室的天数,m 为操作次数。

接下来一行,共包含 n 个整数,第 i 个整数表示第i 天剩余教室数目为 ai 个。

接下来 m 行,每行的第一个整数为操作编号(只能为 1 或 2 或 3),接下来包含两个正整数 l 和 r,若操作编号为 1,则接下来再包含一个正整数 d。

【输出格式】

对于每个操作 2 和操作 3,输出一个既约分数(分子与分母互质)表示询问的答案。若答案为 0,请输出“0/1”(不含引号)。

【样例说明】

初始情况下,剩余教室数量为(1, 2, 3, 4, 5)。

第1 次操作为第1 天到第2 天归还3 个教室, 变为(4, 5, 3, 4, 5)。

第2 次操作询问第2 天到第4 天的平均数为(5+3+4)/3 = 4/1。

第3 次操作询问第2 天到第4 天的方差为(1+1+0)/3 = 2/3。

第4 次操作询问第1 天到第5 天的方差为(0.04 + 0.64 +1.44 + 0.04 + 0.64)/5 = 14/25。

$n,m ≤ 10^5$

保证分子的范围不超过64 位带符号整数。

题解

同时也可以是洛谷P1471P5142的题解

(蒟蒻第一篇破千字的题解)

对于我来说,这题实在是太c了。

首先,区间修改可以让我们很容易地想到线段树。然后,区间求平均就迎刃而解了,因为平均数就是区间和除以区间长度,而区间和正是线段树所维护的。

难点是区间方差。

有一句话说得好:解题时,要回到定义中去。(《怎样解题》)

那么,让我们来看看方差的定义:( $\overline{x}$ 为区间平均数)

$S^2=\frac{1}{n}[(x_{1}-\overline{x})^2+(x_{2}-\overline{x})^2+\cdots+(x_{n}-\overline{x})^2]=\frac{1}{n}\sum\limits_{i=1}^{n} (x_{i}-\overline{x})^2$

完全平方公式展开上式,得到:

$S^2=\frac{1}{n}(\sum\limits_{i=1}^{n} x_{i}^2+\sum\limits_{i=1}^{n} 2x_{i}\overline{x}+n*\overline{x}^2)$

很显然,括号中的后两项 $\sum\limits_{i=1}^{n} 2x_{i}\overline{x}$ 和 $n\overline{x}^2$ 都只需要维护区间和就可以算出,但第一项 $\sum\limits_{i=1}^{n} x_{i}^2$ 不可以。观察这个式子就能发现——这不就是区间平方和吗!是的,这也可以使用线段树维护。

接下来,知道怎么维护区间平方和的可以直接看代码,不知道的(比如在写这道题之前的我)建议看看下面这段:

我们发现,当区间 $[$ $l,r$ $]$ 被加上 $d$ 后,区间平方和会这么改变:

由 $\sum\limits_{i=l}^{r} x_{i}^2$ 变为 $\sum\limits_{i=l}^{r} (x_{i}+d)^2$ ,将后式展开,变为:

$\sum\limits_{i=l}^{r} x_{i}^2+\sum\limits_{i=l}^{r} 2x_{i}d+(r-l+1)d^2$

定睛一看:这不解决了吗!第一项就是原来的那个,第二项是我们维护的区间和乘以常数,第三项就是常数!

所以,我们只需要更改一下线段树的更新 $(update)$ 函数和下推 $(pushdown)$ 函数,就可以让线段树维护平方和了:

//pushdown函数中:
s[lson].sqsum+=2*s[p].lazy_tag*s[lson].sum+(s[lson].r-s[lson].l+1)*s[p].lazy_tag*s[p].lazy_tag;
//lson表示左孩子,rson表示右孩子;sum是维护的区间和
s[rson].sqsum+=2*s[p].lazy_tag*s[lson].sum+(s[lson].r-s[lson].l+1)*s[p].lazy_tag*s[p].lazy_tag;

我们也不需要重新写一棵线段树(当然你再写一棵线段树也没问题),只需要在原线段树的结构体里面加一个表示平方和的参数 $(sqsum)$ 就可以了。具体见代码。

剩下的就是这道题奇葩的输出了。把区间和/区间方差等于0的先特判一下,若不等于0,则用辗转相除法求出它与区间长度的最大公约数 $(gcd)$ ,然后把两数都除以 $gcd$ 后输出。

好了,该说的都说完了,上代码!

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;//不开long long见祖宗
#define lson (p*2)
#define rson (p*2+1)
const int N=1e5+5;
ll n,m,a[N];
inline ll read()//快读函数,可以快速读取(输入) 
{
	ll s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') w=-1;
		ch=getchar();
	} 
	while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
	return s*w;
}
struct seg{
	ll l;
	ll r;
	ll sum,sqsum;
	ll lazy_tag;
}s[N<<2];
inline ll &S(int p)
{
	return s[p].sum;
}
inline ll &SQ(int p)
{
	return s[p].sqsum;
}
inline ll &R(int p)
{
	return s[p].r;
}
inline ll &L(int p)
{
	return s[p].l;
}
void push_up(int p)
{
	S(p)=S(lson)+S(rson);
    	SQ(p)=SQ(lson)+SQ(rson);
}
void push_down(int p)
{
	if(s[p].lazy_tag)
	{
		SQ(lson)+=2*s[p].lazy_tag*S(lson)+(R(lson)-L(lson)+1)*s[p].lazy_tag*s[p].lazy_tag;
		S(lson)+=(R(lson)-L(lson)+1)*s[p].lazy_tag;
        	s[lson].lazy_tag+=s[p].lazy_tag;
        	SQ(rson)+=2*s[p].lazy_tag*S(rson)+(R(rson)-L(rson)+1)*s[p].lazy_tag*s[p].lazy_tag;
        	S(rson)+=(R(rson)-L(rson)+1)*s[p].lazy_tag;
        	s[rson].lazy_tag+=s[p].lazy_tag;
        	s[p].lazy_tag=0;
	}
}
void build(int p,int l,int r)
{
	if(l==r)
	{
		s[p]=(seg){l, r, a[l], a[l]*a[l], 0};
        	return;
	}
	int mid=(l+r)/2;
    	build(lson, l, mid);
    	build(rson, mid+1, r);
    	L(p)=l;
    	R(p)=r;
    	push_up(p);
}
void update(int p, int l, int r, int d)
{
	if(l<=L(p)&&R(p)<=r)
	{
        	SQ(p)+=2*d*S(p)+(R(p)-L(p)+1)*d*d;
        	S(p)+=(R(p)-L(p)+1)*d;
        	s[p].lazy_tag+=d;
        	return;
    	}
    	push_down(p);
    	int mid=(R(p)+L(p))/2;
    	if(l<=mid) update(lson, l, r, d);
    	if(r>mid) update(rson, l, r, d);
    	push_up(p);
}
ll query(int p,int l,int r)
{
	if(l<=L(p)&&R(p)<=r)
	{
        	return S(p);
    	}
    	ll res=0;
    	push_down(p);
    	int mid=(R(p)+L(p))/2;
    	if(l<=mid) res+=query(lson, l, r);
    	if(r>mid) res+=query(rson, l, r);
    	return res;
}
ll upquery(int p,int l,int r)
{
	if(l<=L(p)&&R(p)<=r)
	{
        	return SQ(p);
    	}
    	ll res=0;
    	push_down(p);
    	int mid=(R(p)+L(p))/2;
    	if(l<=mid) res+=upquery(lson, l, r);
    	if(r>mid) res+=upquery(rson, l, r);
    	return res;
}
ll gcd(ll a,ll b)
{
	while(a%b)
	{
		ll r=a%b;
		a=b;
		b=r;
	}
	return b;
}
void printave(ll l,ll r)
{
	ll a=query(1,l,r);
	ll b=r-l+1;
    	if(a==0) printf("0/1\n");
    	else
	{
        	if(a<0)
		{
			printf("-");
			a=-a;
		} 
        	ll d=gcd(a,b);
        	a/=d;
		b/=d;
        	printf("%lld/%lld\n",a,b);
    	}
}
void printfc(ll l,ll r)
{
	ll t=query(1,l,r);
	ll a=(r-l+1)*upquery(1,l,r)-t*t;
	ll b=(r-l+1)*(r-l+1);
    	if(a==0) printf("0/1\n");
    	else
	{
        	if(a<0)
		{
			printf("-");
			a=-a;
		} 
        	ll d=gcd(a,b);
        	a/=d;
		b/=d;
        	printf("%lld/%lld\n",a,b);
    	}
}
int main()
{
	n=read();
	m=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
	}
	build(1,1,n);
	while(m--)
	{
		ll op;
		op=read();
		if(op==1)
		{
			ll l,r,val;
			l=read();
			r=read();
			val=read();
			update(1,l,r,val);
		}
		else if(op==2)
		{
			ll l,r;
			l=read();
			r=read();
			printave(l,r);
		}
		else
		{
			ll l,r;
			l=read();
			r=read();
			printfc(l,r);
		}
	}
}

这道题实在是太c了。