两道线段树入门题

哇,虽然上一篇博客是在十月,但感觉好像有三个月没写博客的样子。
主要是之前太菜,不愿面对,加上最近犯懒,没有坚持写。

P3372 【模板】线段树 1

题意

题目描述
如题,已知一个数列,你需要进行下面两种操作:
1.将某区间每一个数加上x
2.求出某区间每一个数的和
输入格式
第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。
第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。
接下来M行每行包含3或4个整数,表示一个操作,具体如下:
操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k
操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和
输出格式
输出包含若干行整数,即为所有操作2的结果。

代码

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int maxn=100005;
typedef long long ll;
struct SegTreeNode
{
	ll val;
	ll lazyTag;//延迟标记:储存节点的修改情况
	SegTreeNode()
	{
		val=0;
		lazyTag=0;
	}
} segTree[maxn<<2];//完全二叉树(有没用到的节点),一倍可能炸
inline void pushup(int root)
{
	segTree[root].val=segTree[root*2].val+segTree[root*2+1].val;
}
inline void pushdown(int root,int l,int r)//当前节点标志域向孩子节点传递
{//root为当前节点下标
	if(segTree[root].lazyTag)
	{
		segTree[root*2].lazyTag+=segTree[root].lazyTag;
		segTree[root*2+1].lazyTag+=segTree[root].lazyTag;
		int mid=(l+r)>>1;
		segTree[root*2].val+=segTree[root].lazyTag*(mid-l+1);
		segTree[root*2+1].val+=segTree[root].lazyTag*(r-mid);
		segTree[root].lazyTag=0;//消掉该点的标记
	}
}
void build(int root,ll arr[],int ist,int ied)
{//当前根节点
	segTree[root].lazyTag=0;
	if(ist==ied)//叶子节点
		segTree[root].val=arr[ist];
	else
	{
		int mid=(ist+ied)>>1;
		build(root*2,arr,ist,mid);
		build(root*2+1,arr,mid+1,ied);
		pushup(root);
	}
}
void update(int root,int nst,int ned,int ust,int ued,ll num)
{//nst:当前区间,ust:更新区间
	if(ust>ned||ued<nst)
		return;
	if(ust<=nst&&ned<=ued)//子区间更新完成,
	{
		segTree[root].lazyTag+=num;//更新值记录在lazyTag
		segTree[root].val+=(ned-nst+1)*num;
		return;
	}
//	printf("l=%d,r=%d\n",nst,ned);
	pushdown(root,nst,ned);//标记向下传递
	int mid=(nst+ned)>>1;
//	cout<<"HERE1"<<endl;
//	system("pause");
	update(root*2,nst,mid,ust,ued,num);
//	cout<<"HERE2"<<endl;
	update(root*2+1,mid+1,ned,ust,ued,num);
//	cout<<"!!!"<<endl;
	pushup(root);
}
ll query(int root,int nst,int ned,int qst,int qed)//区间查询
{//n:当前区间,q:查询区间
//	cout<<"HAHAHA"<<endl;
	if(qst>ned||qed<nst)//没有交集
		return 0;
	if(qst<=nst&&qed>=ned)//覆盖该区间,返回此区间值
		return segTree[root].val;
	pushdown(root,nst,ned);
	int mid=(nst+ned)>>1;
	return query(root*2,nst,mid,qst,qed)+query(root*2+1,mid+1,ned,qst,qed);
//	cout<<"???"<<endl;
}
ll a[maxn];
int main()
{
	int n,m,x,y;
	ll k;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	build(1,a,1,n);
	while(m--)
	{
		int flag;
		scanf("%d",&flag);
		if(flag==1)
		{
			scanf("%d%d%lld",&x,&y,&k);
			update(1,1,n,x,y,k);
		}
		else
		{
			scanf("%d%d",&x,&y);
			printf("%lld\n",query(1,1,n,x,y));
		}
	}
	return 0;
}

很裸的线段树模板题,之前区间端点的判断写错,无限递归疯狂RE(应该是线段树的常见错误了)。

P3373 【模板】线段树 2

题意

题目描述
如题,已知一个数列,你需要进行下面三种操作:
1.将某区间每一个数乘上x
2.将某区间每一个数加上x
3.求出某区间每一个数的和
输入格式
第一行包含三个整数N、M、P,分别表示该数列数字的个数、操作的总个数和模数。
第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。
接下来M行每行包含3或4个整数,表示一个操作,具体如下:
操作1: 格式:1 x y k 含义:将区间[x,y]内每个数乘上k
操作2: 格式:2 x y k 含义:将区间[x,y]内每个数加上k
操作3: 格式:3 x y 含义:输出区间[x,y]内每个数的和对P取模所得的结果
输出格式
输出包含若干行整数,即为所有操作3的结果。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=100005;
struct node
{
	ll val,add,mul;
} tree[maxn<<2];
ll a[maxn],p;
inline void pushup(int root)
{
	tree[root].val=(tree[root<<1].val+tree[(root<<1)+1].val)%p;
}
void build(int root,ll arr[],int l,int r)
{
	tree[root].add=0;
	tree[root].mul=1;
	if(l==r)
		tree[root].val=arr[l];
	else{
		int mid=l+r>>1;
		build(root<<1,arr,l,mid);
		build((root<<1)+1,arr,mid+1,r);
		pushup(root);
	}
}
inline void pushdown(int root,int l,int r)
{
	int mid=l+r>>1;
//子节点的值=原值*父节点mul+父节点add*区间长度
	tree[root<<1].val=((tree[root<<1].val*tree[root].mul)%p+(tree[root].add*(mid-l+1))%p)%p;//更新子节点的值
	tree[(root<<1)+1].val=((tree[(root<<1)+1].val*tree[root].mul)%p+(tree[root].add*(r-mid))%p)%p;
//维护子节点的lazyTag,子节点mul=与父结点mul累乘
	tree[root<<1].mul=(tree[root<<1].mul*tree[root].mul)%p;
	tree[(root<<1)+1].mul=(tree[(root<<1)+1].mul*tree[root].mul)%p;
//子节点add=add*父节点mul+父节点add
	tree[root<<1].add=((tree[root<<1].add*tree[root].mul)%p+tree[root].add)%p;
	tree[(root<<1)+1].add=((tree[(root<<1)+1].add*tree[root].mul)%p+tree[root].add)%p;

	tree[root].add=0;
	tree[root].mul=1;
}
void update(int root,int nst,int ned,int ust,int ued,ll addnum,ll mulnum)
{
	if(nst>ued||ust>ned)
		return;
	if(ust<=nst&&ned<=ued)
	{
		tree[root].val=((tree[root].val*mulnum)%p+(addnum*(ned-nst+1))%p)%p;
		tree[root].add=(tree[root].add*mulnum+addnum)%p;
		tree[root].mul=(tree[root].mul*mulnum)%p;
		return;
	}
	pushdown(root,nst,ned);
	int mid=nst+ned>>1;
	update(root<<1,nst,mid,ust,ued,addnum,mulnum);
	update((root<<1)+1,mid+1,ned,ust,ued,addnum,mulnum);
	pushup(root);
}
ll query(int root,int nst,int ned,int qst,int qed)
{
	if(qst>ned||nst>qed)
		return 0;
	if(qst<=nst&&ned<=qed)
	{
		return tree[root].val%p;
	}
	pushdown(root,nst,ned);
	int mid=nst+ned>>1;
	return (query(root<<1,nst,mid,qst,qed)+query((root<<1)+1,mid+1,ned,qst,qed))%p;
}
int main()
{
//	freopen("P3373.in","r",stdin);
	int n,m;
	scanf("%d%d%lld",&n,&m,&p);
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	build(1,a,1,n);
//	cout<<"build finished"<<endl;
	while(m--)
	{
		int flag,x,y;
		ll k;
		scanf("%d",&flag);
		if(flag==1)
		{
			scanf("%d%d%lld",&x,&y,&k);
//			cout<<"flag="<<flag<<endl;
			update(1,1,n,x,y,0,k);
//			cout<<"flag1"<<endl;
		}
		else if(flag==2)
		{
			scanf("%d%d%lld",&x,&y,&k);
//			cout<<"flag="<<flag<<endl;
			update(1,1,n,x,y,k,1);
//			cout<<"flag2"<<endl;
		}
		else{
			scanf("%d%d",&x,&y);
			printf("%lld\n",query(1,1,n,x,y)%p);
		}
	}
	return 0;
}

这道题混合了区间加/乘上一个数,所以怎么写好pushdown,如何处理加法与乘法的优先级是个难点。
从下午三点调到晚上八点多才调好。

2019年11月6日20点41分

发布了71 篇原创文章 · 获赞 12 · 访问量 7544

猜你喜欢

转载自blog.csdn.net/Miaplacidus/article/details/102942676