P3373 【模板】线段树 2 题解

题目描叙

题目描述

如题,已知一个数列,你需要进行下面三种操作:

  • 将某区间每一个数乘上 x

  • 将某区间每一个数加上 x

  • 求出某区间每一个数的和

输入格式

第一行包含三个整数 n,m,p,分别表示该数列数字的个数、操作的总个数和模数。

第二行包含 nn 个用空格分隔的整数,其中第 ii 个数字表示数列第 ii 项的初始值。

接下来 mm 行每行包含若干个整数,表示一个操作,具体如下:

操作 11: 格式:1 x y k 含义:将区间 [x,y] 内每个数乘上 k

操作 22: 格式:2 x y k 含义:将区间 [x,y]内每个数加上 k

操作 33: 格式:3 x y 含义:输出区间 [x,y]内每个数的和对 p 取模所得的结果

输出格式

输出包含若干行整数,即为所有操作 3 的结果。

数据范围

对于 30% 的数据:n \le 8n≤8,m \le 10m≤10
对于 70% 的数据:n \le 10^3n≤103,m \le 10^4m≤104
对于 100% 的数据:n \le 10^5n≤105,m \le 10^5m≤105

除样例外,p = 571373

做题思路

看到题目的要求,很自然就联想到线段树的懒标记,当然这道题如果只有一个加法的懒标记是不够的,还需要增加一个乘法的懒标记。

结构体

struct node{
	int l,r;//表示[l,r]区间
	long long w,f,m;
    //w为这个区间中所有数的和
    //f为这个区间加法的懒标志	
    //m为这个区间乘法的懒标志
    node()
	{
		l=r=w=f=0;m=1;
	 } 
};

一般线段树的下拉操作是

void down(int k)
{
//tree[i]是node数组,用于存储线段树
int i=2*k,j=2*k+1;
tree[i].f+=tree[k].f;
tree[j].f+=tree[k].f;
tree[i].w+=tree[k].f*(tree[i].r-tree[i].l+1);
tree[j].w+=tree[k].f*(tree[j].r-tree[j].l+1);
tree[k].f=0;
}

对于这道题自然能联想到下拉操作会变成这样

void down(int k)
{
int i=2*k,j=2*k+1;
tree[i].f+=tree[k].f;
tree[j].f+=tree[k].f;
tree[i].m*=tree[k].m;
tree[j].m*=tree[k].m;
tree[i].w+=tree[k].f*(tree[i].r-tree[i].l+1);
tree[i].w*=tree[k].m;
tree[j].w+=tree[k].f*(tree[j].r-tree[j].l+1);
tree[j].w*=tree[k].m;
tree[k].m=1;
tree[k].f=0;
}

 上面的下拉代码明显存在缺陷(虽然我就是死在这里),上面的代码没有考虑到乘法懒标记和加法懒标记的继承先后问题,如果还不明白,可以想一下,连续的两次操作:先乘再加,上述代码明显无法达到目的。下面才是正确的代码

void down(int k)
{
	int i=2*k,j=2*k+1;
	int d1=(tree[i].r-tree[i].l+1)%p;
	int d2=(tree[j].r-tree[j].l+1)%p;
	tree[i].w=((tree[i].w*tree[k].m)%p
			  +(tree[k].f*d1)%p)%p;
	tree[j].w=((tree[j].w*tree[k].m)%p
	          +(tree[k].f*d2)%p)%p;
	tree[i].m=(tree[i].m*tree[k].m)%p;
	tree[j].m=(tree[j].m*tree[k].m)%p;
	tree[i].f=((tree[i].f*tree[k].m)%p+
	            tree[k].f)%p;
	tree[j].f=((tree[j].f*tree[k].m)%p+
				tree[k].f)%p;
	tree[k].m=1;
	tree[k].f=0;
}

我相信看到这部分跟我反一样错误的人已经明白了 

代码

//这道题还真是厉害了呢 
#include<iostream>
using namespace std;
struct node{
	int l,r;
	long long w,f,m;
	node()
	{
		l=r=w=f=0;m=1;
	 } 
};
const int maxn=4e5+9;
node tree[maxn];
int p;
void build(int l,int r,int k);
void multiply(int k,int a,int b,long long x);
void add(int k,int a,int b,long long x);
long long ask(int k,int a,int b);
void down(int k);
void print(int k);
int main()
{
	int n,m;
	scanf("%d%d%d",&n,&m,&p);
	build(1,n,1);
//	print(1);
//	printf("\n");
	int flag,x,y,k;
	while(m--)
	{
		scanf("%d%d%d",&flag,&x,&y);
		if(flag==1)
		{
			scanf("%d",&k); 
			//相乘的
			multiply(1,x,y,k%p); 
			//print(1);
		//	printf("\n");
		}
		else if(flag==2)
		{
			scanf("%d",&k);
			add(1,x,y,k%p);
		//	print(1);
		//	printf("\n");
		}
		else printf("%lld\n",ask(1,x,y));
	}
}
long long ask(int k,int a,int b)
{
	int l=tree[k].l,r=tree[k].r;
	if(l==a&&r==b)
	return tree[k].w%p;
	if(tree[k].f||tree[k].m!=1)
	down(k);
	int mid=(l+r)/2;
	long long ans;
	if(mid>=b)
	ans=ask(2*k,a,b)%p;
	else if(a>mid)
	ans=ask(2*k+1,a,b)%p;
	else
	ans=(ask(2*k,a,mid)%p
	   +ask(2*k+1,mid+1,b)%p)%p;
	tree[k].w=(tree[2*k].w+tree[2*k+1].w)%p;
	return ans;
}
void down(int k)
{
	int i=2*k,j=2*k+1;
	int d1=(tree[i].r-tree[i].l+1)%p;
	int d2=(tree[j].r-tree[j].l+1)%p;
	tree[i].w=((tree[i].w*tree[k].m)%p
			  +(tree[k].f*d1)%p)%p;
	tree[j].w=((tree[j].w*tree[k].m)%p
	          +(tree[k].f*d2)%p)%p;
	tree[i].m=(tree[i].m*tree[k].m)%p;
	tree[j].m=(tree[j].m*tree[k].m)%p;
	tree[i].f=((tree[i].f*tree[k].m)%p+
	            tree[k].f)%p;
	tree[j].f=((tree[j].f*tree[k].m)%p+
				tree[k].f)%p;
	tree[k].m=1;
	tree[k].f=0;
}
void add(int k,int a,int b,long long x)
{
	int l=tree[k].l,r=tree[k].r;
	int d1=(r-l+1)%p;
	if(l==a&&r==b)
	{
		tree[k].f=(tree[k].f+x)%p;
		tree[k].w=(tree[k].w+d1*x)%p;
		return;
	}
	if(tree[k].m!=1||tree[k].f)
	down(k);
	int mid=(l+r)/2;
	if(mid>=b)
	add(2*k,a,b,x);
	else if(a>mid)
	add(2*k+1,a,b,x);
	else
	{
		add(2*k,a,mid,x);
		add(2*k+1,mid+1,b,x);
	}
	tree[k].w=(tree[2*k].w+tree[2*k+1].w)%p;
	return;
}
void multiply(int k,int a,int b,long long x)
{
	int l=tree[k].l,r=tree[k].r;
	if(l==a&&r==b)
	{
		tree[k].m=(tree[k].m*x)%p;
		tree[k].w=(tree[k].w*x)%p;
		tree[k].f=(tree[k].f*x)%p; 
		return;
	}
	if(tree[k].m!=1||tree[k].f)
	down(k);
	int mid=(l+r)/2;
	if(mid>=b)
	multiply(2*k,a,b,x);
	else if(a>mid)
	multiply(2*k+1,a,b,x);
	else
	{
		multiply(2*k,a,mid,x);
		multiply(2*k+1,mid+1,b,x);
	} 
	tree[k].w=(tree[2*k].w+tree[2*k+1].w)%p;
	return;
}
void build(int l,int r,int k)
{
	tree[k].l=l;tree[k].r=r;
	if(l==r)
	{
		scanf("%lld",&tree[k].w);
		tree[k].w%=p;
		return;
	}
	int mid=(l+r)/2;
	build(l,mid,2*k);
	build(mid+1,r,2*k+1);
	tree[k].w=(tree[2*k].w+tree[2*k+1].w)%p;
	return;
}
void print(int k)
{
	int l=tree[k].l,r=tree[k].r;
	printf("[%d,%d]的权值为%lld\n",l,r,tree[k].w);
	if(l==r)return;
	print(2*k);
	print(2*k+1);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值