【题解】「AHOI2009」维护序列(线段树)

2024-02-08 03:18

本文主要是介绍【题解】「AHOI2009」维护序列(线段树),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

题面

【题目描述】
老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成。 有长为 N N N的数列,不妨设为 a 1 , a 2 , … , a N a_1,a_2,…,a_N a1,a2,,aN。有如下三种操作形式:
( 1 ) (1) (1)把数列中的一段数全部乘一个值;
( 2 ) (2) (2)把数列中的一段数全部加一个值;
( 3 ) (3) (3)询问数列中的一段数的和,由于答案可能很大,你只需输出这个数模 P P P的值。
【输入】
第一行两个整数 N N N P ( 1 ≤ P ≤ 1000000000 ) P(1≤P≤1000000000) P(1P1000000000
第二行含有 N N N个非负整数,从左到右依次为 a 1 , a 2 , … , a N , ( 0 ≤ a i ≤ 1000000000 , 1 ≤ i ≤ N ) a_1,a_2,…,a_N, (0≤a_i≤1000000000,1≤i≤N) a1,a2,,aN,(0ai1000000000,1iN)
第三行有一个整数 M M M,表示操作总数。
从第四行开始每行描述一个操作,输入的操作有以下三种形式:
操作1:“1 t g c”(不含双引号)。表示把所有满足 t ≤ i ≤ g t≤i≤g tig a i a_i ai改为 a i × c ( 1 ≤ t ≤ g ≤ N , 0 ≤ c ≤ 1000000000 ) a_i×c(1≤t≤g≤N,0≤c≤1000000000) ai×c(1tgN,0c1000000000)
操作2:“2 t g c”(不含双引号)。表示把所有满足 t ≤ i ≤ g t≤i≤g tig a i a_i ai改为 a i + c ( 1 ≤ t ≤ g ≤ N , 0 ≤ c ≤ 1000000000 ) a_i+c (1≤t≤g≤N,0≤c≤1000000000) ai+c(1tgN,0c1000000000)
操作3:“3 t g”(不含双引号)。询问所有满足 t ≤ i ≤ g t≤i≤g tig a i a_i ai的和模P的值 ( 1 ≤ t ≤ g ≤ N ) (1≤t≤g≤N) (1tgN)
同一行相邻两数之间用一个空格隔开,每行开头和末尾没有多余空格。
【输出】
对每个操作 3 3 3,按照它在输入中出现的顺序,依次输出一行一个整数表示询问结果。
【样例输入】

7 43
1 2 3 4 5 6 7
5
1 2 5 5
3 2 4
2 3 7 9
3 1 3
3 4 7

【样例输出】

2
35
8

【样例解释】
初始时数列为 ( 1 , 2 , 3 , 4 , 5 , 6 , 7 ) (1,2,3,4,5,6,7) (1,2,3,4,5,6,7)
经过第 1 1 1次操作后,数列为 ( 1 , 10 , 15 , 20 , 25 , 6 , 7 ) (1,10,15,20,25,6,7) (1,10,15,20,25,6,7)
对第 2 2 2次操作,和为 10 + 15 + 20 = 45 10+15+20=45 10+15+20=45,模 43 43 43的结果是 2 2 2
经过第 3 3 3次操作后,数列为 ( 1 , 10 , 24 , 29 , 34 , 15 , 16 ) (1,10,24,29,34,15,16) (1,10,24,29,34,15,16)
对第 4 4 4次操作,和为 1 + 10 + 24 = 35 1+10+24=35 1+10+24=35,模 43 43 43的结果是 35 35 35
对第 5 5 5次操作,和为 29 + 34 + 15 + 16 = 94 29+34+15+16=94 29+34+15+16=94,模 43 43 43的结果是 8 8 8
测试数据规模如下表所示:

数据编号12345678910
N=10100010001000060000700008000090000100000100000
M=10100010001000060000700008000090000100000100000

算法分析

线段树模板题
区间操作只有加法,我们可以使用 l a z y lazy lazy标记区间,例如:【题解】「POJ3468」A Simple Problem with Integers(线段树)
如果存在两种区间操作,怎么办呢?可以用同样的方法,进行lazy标记,使用两个lazy标记即可。
但是,现在有一个问题,如果线段树第 k k k个结点,乘法标记 l a z y [ k ] = s lazy[k]=s lazy[k]=s,加法标记 l a z y 2 [ k ] = t lazy2[k]=t lazy2[k]=t,如果要计算第 k k k个结点的区间值怎么计算呢?先计算乘法,还是先计算加法呢?顺序不一样,结果就不一样。

在这里先计算乘法,再计算加法:
k k k个结点,乘法标记 l a z y [ k ] = s lazy[k]=s lazy[k]=s,加法标记 l a z y 2 [ k ] = t lazy2[k]=t lazy2[k]=t,现在要对第 k k k个结点的区间整体乘以 T T T,那么可以这样更新标记:
lazy[k]=s*T;
lazy2[k]=t*T;
计算第k个结点区间值时先计算乘法,再计算加法就不会出现错误,因为加数已经提前乘以了 T T T
如果现在要对第k个结点的区间整体加上 P P P,那么只需要更新:
lazy2[k]=t+P;
因为加法对乘法是没有影响的。

解决加法和乘法操作,使用两个 l a z y lazy lazy标记,对于乘法,需要同时更新乘法标记和加法标记,对于加法,只需要更新加法标记,计算时,先计算乘法,再计算加法,因为加数提前计算了乘法。

参考程序

#include<bits/stdc++.h>
#define N 100100
using namespace std;
long long s[4*N],lazy[4*N],lazy2[4*N];//s[]表示区间和,lazy[]标记乘法,lazy[]标记加法 
int n,m,M;
void built(int k,int l,int r)
{lazy[k]=1;				//乘法标记默认为1 lazy2[k]=0;if(l==r){scanf("%lld",&s[k]);return;}int mid=(l+r)/2;built(2*k,l,mid);built(2*k+1,mid+1,r);s[k]=s[2*k]+s[2*k+1];
}
void pushdown(int k,int l,int r)
{//先计算乘法,乘法对加法的影响已经处理一次了if(lazy[k]!=1){//左孩子s[2*k]=(s[2*k]*lazy[k])%M;lazy[2*k]=(lazy[2*k]*lazy[k])%M;lazy2[2*k]=(lazy2[2*k]*lazy[k])%M;	//更新乘法对加法的影响 //右孩子 s[2*k+1]=(s[2*k+1]*lazy[k])%M;lazy[2*k+1]=(lazy[2*k+1]*lazy[k])%M;lazy2[2*k+1]=(lazy2[2*k+1]*lazy[k])%M;	//更新乘法对加法的影响//自己清1lazy[k]=1;}	int mid=(l+r)/2;if(lazy2[k]!=0){//左孩子s[2*k]=(s[2*k]+(mid-l+1)*lazy2[k])%M;lazy2[2*k]=(lazy2[2*k]+lazy2[k])%M;//右孩子 s[2*k+1]=(s[2*k+1]+(r-mid)*lazy2[k])%M;lazy2[2*k+1]=(lazy2[2*k+1]+lazy2[k])%M;//自己清0lazy2[k]=0;}return;
} 
void update(int k,int l,int r,int x,int y,int t1,int t2)	//区间修改,加上t1,乘以t2
{if(l>y||r<x) return;if(x<=l&&r<=y){if(t1>=0) 	//区间加法 {s[k]=((s[k]%M)+(r-l+1)*(t1%M))%M;lazy2[k]+=t1;} else		//区间乘法{s[k]=((s[k]%M)*(t2%M))%M;lazy[k]=(lazy[k]*t2)%M;lazy2[k]=(lazy2[k]*t2)%M;		//乘法对加法有影响,相当于加数*t2 }return; }pushdown(k,l,r);			//如果有标记,先更新 int mid=(l+r)/2;update(2*k,l,mid,x,y,t1,t2);update(2*k+1,mid+1,r,x,y,t1,t2);s[k]=((s[2*k]%M)+(s[2*k+1]%M))%M;		//回溯更新区间和 
} 
long long ask(int k,int l,int r,int x,int y)
{if(y<l||x>r) return 0;if(x<=l&&r<=y) return s[k];pushdown(k,l,r);			//未更新的先更新 int mid=(l+r)/2;return (ask(2*k,l,mid,x,y)+ask(2*k+1,mid+1,r,x,y))%M;
}
int main()
{scanf("%d%d",&n,&M);built(1,1,n);int k,t,g;long long c;scanf("%d",&m);while(m--){scanf("%d",&k);if(k==1) 			//区间乘 {scanf("%d%d%lld",&t,&g,&c);update(1,1,n,t,g,-1,c);	}if(k==2)			//区间加 {scanf("%d%d%lld",&t,&g,&c);update(1,1,n,t,g,c,1);}if(k==3){scanf("%d%d",&t,&g);printf("%lld\n",ask(1,1,n,t,g)); }}return 0;
} 

这篇关于【题解】「AHOI2009」维护序列(线段树)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/689751

相关文章

poj3468(线段树成段更新模板题)

题意:包括两个操作:1、将[a.b]上的数字加上v;2、查询区间[a,b]上的和 下面的介绍是下解题思路: 首先介绍  lazy-tag思想:用一个变量记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。 比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

hdu1689(线段树成段更新)

两种操作:1、set区间[a,b]上数字为v;2、查询[ 1 , n ]上的sum 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<queue>#include<set>#include<map>#include<stdio.h>#include<stdl

hdu 1754 I Hate It(线段树,单点更新,区间最值)

题意是求一个线段中的最大数。 线段树的模板题,试用了一下交大的模板。效率有点略低。 代码: #include <stdio.h>#include <string.h>#define TREE_SIZE (1 << (20))//const int TREE_SIZE = 200000 + 10;int max(int a, int b){return a > b ? a :

hdu 1166 敌兵布阵(树状数组 or 线段树)

题意是求一个线段的和,在线段上可以进行加减的修改。 树状数组的模板题。 代码: #include <stdio.h>#include <string.h>const int maxn = 50000 + 1;int c[maxn];int n;int lowbit(int x){return x & -x;}void add(int x, int num){while

uva 10131 最长子序列

题意: 给大象的体重和智商,求体重按从大到小,智商从高到低的最长子序列,并输出路径。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#include <stack>#include <vect

poj 1127 线段相交的判定

题意: 有n根木棍,每根的端点坐标分别是 px, py, qx, qy。 判断每对木棍是否相连,当他们之间有公共点时,就认为他们相连。 并且通过相连的木棍相连的木棍也是相连的。 解析: 线段相交的判定。 首先,模板中的线段相交是不判端点的,所以要加一个端点在直线上的判定; 然后,端点在直线上的判定这个函数是不判定两个端点是同一个端点的情况的,所以要加是否端点相等的判断。 最后

HDU4737线段树

题目大意:给定一系列数,F(i,j)表示对从ai到aj连续求或运算,(i<=j)求F(i,j)<=m的总数。 const int Max_N = 100008 ;int sum[Max_N<<2] , x[Max_N] ;int n , m ;void push_up(int t){sum[t] = sum[t<<1] | sum[t<<1|1] ;}void upd

zoj 1721 判断2条线段(完全)相交

给出起点,终点,与一些障碍线段。 求起点到终点的最短路。 枚举2点的距离,然后最短路。 2点可达条件:没有线段与这2点所构成的线段(完全)相交。 const double eps = 1e-8 ;double add(double x , double y){if(fabs(x+y) < eps*(fabs(x) + fabs(y))) return 0 ;return x + y ;

圆与线段的交点

poj 3819  给出一条线段的两个端点,再给出n个圆,求出这条线段被所有圆覆盖的部分占了整条线段的百分比。 圆与线段的交点 : 向量AB 的参数方程  P = A + t * (B - A)      0<=t<=1 ; 将点带入圆的方程即可。  注意: 有交点 0 <= t <= 1 ; 此题求覆盖的部分。 则 若求得 t  满足 ; double ask(d