【线段树】幼儿园

【题目描述】
幼儿园里的小朋友们正在和老师玩一个有趣的游戏。小朋友们被分成了 N 小组,初始时第i小组的人数为 ai 。老师先选定一些编号在[l,r]内的小组,然后开始与这段连续的小组的一次游戏。游戏是这样进行的:每次游戏分成若干轮,每一轮老师转过身去,[l,r]中的小组的小朋友们需要在这时通过选举选出 3 个来自不同小组的小朋友上前领老师的糖果, 并且为了体现公平,以下情形被视为犯规:某一轮选出了 3 个人领糖果,而后来这 3 个人又在同一 轮被选举出来领糖果。现在老师会不时地问你:假如他与编号在[l,r]内的小组做一次游戏,则最多能玩多少轮。小组不是固定的,人数会不时发生变化。

【输入描述】
第一行两个整数 N, M ,分别为小组数目和事件数目。
第二行 N 个整数表示 ai 。
接下来 M 行,每行描述一个事件。事件为以下两类之一(第一个数为事件类型) :
1 l r 表示询问
2 id p 表示第id 个小组的人数变成 p 。

【输出描述】
对于每一个询问操作,输出一行一个整数为答案。因为答案可能很大,只需要输出其对 1e9 + 7 取模后的结果即可。

【思路】

首先我们假设只有三个组,那么答案应该是三个组的人数的乘积,那么推广到任意大小区间:
a n s [ l , r ] = a [ i ] a [ j ] a [ k ] ( l < = i , j , k < = r i , j , k ) ans[l,r]=\sum a[i]*a[j]*a[k](l<=i,j,k<=r且i,j,k不等)
对于一个左区间和一个右区间的信息,我们思考一下如何合并这两个区间。那么大区间的答案应该包括以下几个部分:第一,三个数全部在左或右区间,直接把左右区间的答案累加进大区间的值即可;第二,两个区间分别有1个数,2个数。此时稍加 分析并进行简单的 数学推导可得,答案是一个区间的和乘另一个区间的两两之间积的和。那么问题只剩下如何维护一个区间每个数两两之间积的和了。此时再分两种情况,当两个数都在左或右区间时,将两个区间的值累加即可,或者两个区间分别有一个数,此时就把两边区间的和相乘即可。那么我们 需要维护三个值——区间的和,两两之间积的和,区间内任意三个数之间积的和就可以完成本题了。
代码:

#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<string>
#include<vector>
#define re register
#define lc (p<<1)
#define rc (p<<1|1)
#define len(p) (t[p].r-t[p].l+1)
using namespace std;
long long n,m,a[100001],b,c,x;
struct node{
	long long l,r;
	long long sum1;
	long long sum2;
	long long sum3;
}t[400001];
long long mod=1e9+7;
inline node merge(node a,node b)
{
	node ans;
	ans.l=a.l;
	ans.r=b.r;
	ans.sum1=a.sum1+b.sum1;ans.sum1%=mod;
	ans.sum3=a.sum1*b.sum2%mod+a.sum2*b.sum1%mod+a.sum3+b.sum3;ans.sum3%=mod;
	ans.sum2=a.sum2+b.sum2+a.sum1*b.sum1%mod;ans.sum2%=mod;
	return ans;
}
void build(long long p,long long l,long long r)
{
	t[p].l=l;
	t[p].r=r;
	if(l==r)
	{
		t[p].sum1=a[l];
		return;
	}
	long long mid=(l+r)>>1;
	build(lc,l,mid);
	build(rc,mid+1,r);
	t[p]=merge(t[lc],t[rc]);
}
void change(long long p,long long k,long long v)
{
	if(t[p].l==t[p].r)
	{
		t[p].sum1=v;
		return;
	}
	long long mid=(t[p].l+t[p].r)>>1;
	if(k<=mid)change(lc,k,v);
	if(k>mid)change(rc,k,v);
	t[p]=merge(t[lc],t[rc]);
}
inline node ask(long long p,long long l, long long r)
{
	if(t[p].l>=l&&t[p].r<=r)
		return t[p];
	long long mid=(t[p].l+t[p].r)>>1;
	if(l<=mid&&r<=mid)return ask(lc,l,r);
	if(r>mid&&l>mid)return ask(rc,l,r);
	return merge(ask(lc,l,r),ask(rc,l,r));
}
int main()
{
	scanf("%lld%lld",&n,&m);
	for(int re i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	build(1,1,n);
	for(int re i=1;i<=m;i++)
	{
		scanf("%lld%lld%lld",&x,&b,&c);
		if(x==1)
			printf("%lld\n",ask(1,b,c).sum3);
		else
			change(1,b,c);
	}
}

是不是很弱智的题呀?

猜你喜欢

转载自blog.csdn.net/weixin_44111457/article/details/86694637