线段树与树状数组总结分析(可能是最终版)

总算是把线段树和树状数组的例题给干完了,晚上思考下该继续做练习还是干别的专题,目前想法是干别的专题,只要每天重新做几道例题,反复做到滚瓜烂熟,遇到时能举一反一就好了。

线段树

1.批量等值修改

前提条件

是要区间修改,区间查询,且修改操作修改的值是相同的,比如批量+1,批量-1.
有一种特例是批量替换,

情景

一般是要对一个数组执行k次操作,每次改变其中一个区间内所有元素的值,然后询问一个区间内所有元素的最值或总和,

例题1区间等值操作

题解代码
void Pushdown(int k){    //更新子树的lazy值,这里是RMQ的函数,要实现区间和等则需要修改函数内容
    if(lazy[k]){    //如果有lazy标记
        lazy[k<<1] += lazy[k];    //更新左子树的lazy值
        lazy[k<<1|1] += lazy[k];    //更新右子树的lazy值
        t[k<<1] += lazy[k];        //左子树的最值加上lazy值
        t[k<<1|1] += lazy[k];    //右子树的最值加上lazy值
        lazy[k] = 0;    //lazy值归0
    }
}

注意懒标记中储存区间修改的值与长度的乘积,大概率开long long

struct node {
    int l, r;
    ll val;
    ll lazy;
}t[N << 2];
void pushdown(node& op, ll lazy) {
    op.val += lazy * (op.r - op.l + 1);
    op.lazy += lazy;
}
void pushdown(int x) {
    if (!t[x].lazy) return;
    pushdown(t[x << 1], t[x].lazy), pushdown(t[x << 1 | 1], t[x].lazy);
    t[x].lazy = 0;
}
void build(int l, int r, int x = 1\没有值传入时,默认初始化为1) {
    t[x] = { l, r, w[l], 0 };
    if (l == r) return;
    int mid = l + r >> 1;
    build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
    pushup(x);
}

void modify(int l, int r, int c, int x = 1) {
    if (l <= tr[x].l && r >= tr[x].r) { pushdown(tr[x], c); return; }\通过打标记的方法来赋值
    pushdown(x);
    操作时遇到了懒标记就处理下(懒的思想,顺路就搞下,不顺路就拖着不干)
    int mid = tr[x].l + tr[x].r >> 1;
    if (l <= mid) modify(l, r, c, x << 1);\modify的递归也变成了和线段树单点修改query里的递归形式,有交集就递归。
    if (r > mid) modify(l, r, c, x << 1 | 1);
    pushup(x);
}

ll ask(int l, int r, int x = 1) {
    if (l <= t[x].l && r >= tr[x].r) return tr[x].val;
    pushdown(x);
    //query的唯一变化就是加上了一个pushdown();
    int mid = tr[x].l + tr[x].r >> 1;
    ll res = 0;
    if (l <= mid) res += ask(l, r, x << 1);
    if (r > mid) res += ask(l, r, x << 1 | 1);
    return res;
}

int main()
{
    int n, m; cin >> n >> m;
    rep(i, n) scanf("%d", &w[i]);
    build(1, n);
    
    while (m--) {
        char op[2]; int l, r; scanf("%s %d %d", op, &l, &r);
        
        if (*op == 'Q') printf("%lld\n", ask(l, r));
        else {
            int c; scanf("%d", &c);
            modify(l, r, c);
        }
    }
    return 0;
}


自己写的acwing式代码
#include 
#include 
#include 
#include 
using namespace std;
const int N = 1e5 + 10;
typedef long long ll;
struct node{
	int l,r;
	ll sum;
	ll lazy;
}tr[N<<2];
int w[N];
传入的lazy写成int类型,导致卡壳非常久。
晚上再写一遍。
注意不要混淆long longint
void pushdown(node &x,ll lazy){
	x.sum += lazy*(x.r - x.l + 1);
	x.lazy += lazy;
}
//一次调用需要从外界赋值,打上lazy,还有一次调用不需要从外界赋值解放lazy,所以分开写。
void pushdown(int u){
	if(!tr[u].lazy) return;
	pushdown(tr[u<<1],tr[u].lazy),pushdown(tr[u<<1|1],tr[u].lazy);
	tr[u].lazy = 0;
}
void pushup(int u){
	tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum;
}
void build(int u,int l,int r){
	tr[u].l = l,tr[u].r = r,tr[u].sum =w[r];
	if(l == r) return;
	int mid = l + r >> 1;
	build(u<<1,l,mid),build(u<<1|1,mid+1,r);
	pushup(u);
}
ll query(int u,int l,int r){
	if(tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
	ll res = 0;
	int mid = tr[u].l + tr[u].r >> 1;
	pushdown(u);
	if(l <= mid) res += query(u<<1,l,r);
	if(r > mid) res += query(u<<1|1,l,r);
	return res;
}
void modify(int u,int l,int r,int v){
	if(tr[u].l >= l && tr[u].r <= r){
		pushdown(tr[u],v);
		return;
	}
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid) modify(u<<1,l,r,v);
	if(r > mid) modify(u<<1|1,l,r,v); 
	pushup(u);
}
int main(){
	int n,m;
	cin >> n >> m;
	for(int i =1;i <= n;i++) scanf("%d",&w[i]);
	build(1,1,n);
	while(m--){
		char op[2];
		int l,r;
		scanf("%s%d%d",op,&l,&r);
		if(*op == 'Q') printf("%lld\n",query(1,l,r));
		else{
			int c;
			scanf("%d",&c);
			modify(1,l,r,c);
		}
	}
	return 0;
}

注意:

线段树的初始化在build里完成,多组数据集时不需要再额外初始化。


例题2线段树区间覆盖,二分查找

区别似乎可以用储存的是什么,还有操作是什么来区分

题解代码

#include 
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 5E4 + 10;
struct node {
    int l, r;
    int val;
    int lazy;
    lazy不能初始化为01的原因,要区分lazy的是否存在,存在时状态为非空还是空
    一般直觉是可以把有花设置为1,无花设置为0,可以用区间长度-查到的数量进行有花和无花数量的查询,所以这个应该不是什么问题,写出来之后再替换下。
}t[N << 2];
void pushdown(node& op, int lazy) {
    op.val = lazy * (op.r - op.l + 1);
    op.lazy = lazy;
    解除储存的懒标记
    理解这个代码的关键点在于这里的符号是等于,而不是+=
	}
void pushdown(int x) {
    if (t[x].lazy == -1) return;
    pushdown(t[x << 1], t[x].lazy), pushdown(t[x << 1 | 1],t[x].lazy);
    t[x].lazy = -1;
    向根部递归,解除所有子树里的节点的懒标记
}

void pushup(int x) {
	t[x].val = t[x << 1].val + t[x << 1 | 1].val; 
}

void build(int l, int r, int x = 1) {
    t[x] = { l, r, 1, -1 };
    if (l == r) return;
    int mid = l + r >> 1;
    build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
    pushup(x);
}

void modify(int l, int r, int c, int x = 1) {
    if (l <= t[x].l && r >= t[x].r) {
        pushdown(t[x], c);
        return;
    }
    pushdown(x);
    int mid = t[x].l + t[x].r >> 1;
    if (l <= mid) modify(l, r, c, x << 1);
    if (r > mid) modify(l, r, c, x << 1 | 1);
    pushup(x);
}

int ask(int l, int r, int x = 1) { //查询[l, r]区间的空花瓶数目
    if (l <= t[x].l && r >= t[x].r) return t[x].val;
    pushdown(x);
    int mid = t[x].l + t[x].r >> 1;
    int res = 0;
    if (l <= mid) res += ask(l, r, x << 1);
    if (r > mid) res += ask(l, r, x << 1 | 1);
    return res;
    1代表花瓶是空的
}

int n, m;
int getindex(int a, int c) { //找到[a, n]区间, 第c个可以放花瓶的位置
    int l = a, r = n;
    while (l < r) {
        int mid = l + r >> 1;
        if (ask(a, mid) >= c) r = mid;
        else l = mid + 1;
    }
    return l;
}
int main()
{
    int T; cin >> T;
    while (T--) {
        scanf("%d %d", &n, &m);
        build(1, n);
        while (m--) {
            int k, x, y; scanf("%d %d %d", &k, &x, &y);
            if (k == 1) {
                int cou = ask(x + 1, n);
                找到总共能插的花瓶数量
                if (!cou) printf("Can not put any one.\n");
                else {
                    int L = getindex(x + 1, 1);
                    找到第一个能插花瓶的下标
                    int R = getindex(x + 1, min(y, cou)); //注意要和cou取一个min
                    找到最后一个插花瓶的地方,或者是最后一个能插花瓶的地方
                    modify(L, R, 0);
                    l到r的数量设置为0,插花。
                    printf("%d %d\n", L - 1, R - 1);
                }
            }
            else {
                int res = y - x + 1 - ask(x + 1, y + 1);
	                x到y的花瓶数量 - x到y的空花瓶数量
	                res就是非空(被清空)的花瓶数量
                modify(x + 1, y + 1, 1);
	                x到y的数量设置为1
	                1代表花瓶是空的
                printf("%d\n", res);
            }
        }
        puts("");
    }
    return 0;
}


经过模仿得到的acwing代码

#include 
#include 
#include 
#include 
using namespace std;
const int N = 5e4 + 10;
统计的是花瓶数量,不需要开long longstruct node{
	int l,r;
	int sum;
	int lazy;
}tr[N<<2];
void pushdown(int u,int lazy){
	批量修改后,子树内所有的节点状态都相等,所以直接用lazy存状态,然后乘区间长度就可以表示出当前节点下的1状态节点个数,直接赋给sum。
	lazy存一个当前节点下的所有节点的状态。
	tr[u].sum = lazy * (tr[u].r - tr[u].l + 1);
	tr[u].lazy = lazy;
}
void pushdown(int u){
	if(tr[u].lazy == -1) return ;
	pushdown(u<<1,tr[u].lazy),pushdown(u<<1|1,tr[u].lazy);
	tr[u].lazy = -1;
}
void pushup(int u){
	
	tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum
}
void build(int u,int l,int r){
build需要和之前的题对比一下
	tr[u] = {l,r,1,-1};
	if(l == r) return;
	int mid = l +  r >> 1;
	build(u<<1,l,mid),build(u<<1|1,mid+1,r);
	pushup(u);
}
void modify(int u,int l,int r,int v){
没什么区别,传统批量等值修改的modify
	if(l <= tr[u].l && tr[u].r <= r){
		pushdown(u,v);
		return;
	}
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid) modify(u<<1,l,r,v);
	if(r > mid) modify(u<<1|1,l,r,v);
	pushup(u);
}
int query(int u,int l,int r){
没什么区别,传统批量等值修改的query
	if(l <= tr[u].l && tr[u].r <= r) return tr[u].sum;
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1;
	int res = 0;
	if(l <= mid) res += query(u<<1,l,r);
	if(r > mid) res += query(u<<1|1,l,r);
	return res;
}
int n,m;
int find(int x,int v){
找a到n的第v个空花瓶。没有第v个会返回n
二分是要看mid和返回值的关系 
	int l = x,r = n;
	while(l < r){
		int mid = l  + r >> 1;
		if(query(1,x,mid) >= v) r = mid;
		else l = mid + 1;
	}
	return l;
}
int main(){
	int t;
	cin >> t;
	while(t--){
		scanf("%d%d",&n,&m);
		build(1,1,n);
		while(m--){
			int op,l,r;
			scanf("%d%d%d",&op,&l,&r);
			
			if(op == 1){
				l++;
				int cnt = query(1,l,n);
//				cout << cnt << endl;
				if(!cnt) printf("Can not put any one.\n");
				else{
					int L = find(l,1);
					
					int R = find(l,min(r,cnt));
					因为是找最后一个能插花的位置,所以要先用花的数量和空花瓶数量比较下,选出最小的一个,保证find查到的返回值位置是空花瓶。
//					cout << L << " " << R << endl;
					modify(1,L,R,0);
					printf("%d %d\n",L-1,R-1);
				}
			}
			else{
				l++,r++;
				int res = r - l + 1 - query(1,l,r);
				modify(1,l,r,1);
				printf("%d\n",res);
			}
		}
		puts("");
	}
	return 0;
}

例题3最大连续区间的维护,给连续区间长度,找端点

与例题2的区别,

此题要求在一个连续的区间上操作,所以是要知道一段区间是否连续,是否足够长,
用节点信息fmax,lmax,rmax来维护区间最大连续,左到右的最大连续,右到左的最大连续

情景:

如果这段时间空闲就安排活动。


因为要找到最左的区间,用深度优先搜索来找。
递归方面倒是和例题2很像,都是有一个左侧空瓶优先。但不会例题2递归做法,还不太懂。


套路

区间合并

前提条件:找连续区间
情景

区间合并,最大连续的情景
每组i向柜台的麋鹿Canmuu申请一组Di连续的房间。
任意两个相邻村庄之间可以通过地道保持连通。八路军司令询问从第 x 个村庄可以到达多少个地道完好的村庄(包括第 x 个村庄本身)。
就是找一段最靠前的符合要求的连续空间分配给每个请求,

应对:
用fmax,lmax,和rmax来维护最大连续。

查询区间,端点如果有优先级
递归优先的情景Canmuu总是选择尽可能小的r值。
就是找一段最靠前的符合要求的连续空间分配给每个请求,

dfs深搜递归查询,有先的放在前面。


一个染色优先度的套路,

前提条件

分色1,和色2,两种颜色
色1优先级高,可以无视色2进行区间染色,优先染无色区间,次优先染色2区间,不能染色1区间
色2不能在色1染色过的地方染色。

情景:

如果没有找到,小明就冒着木叽叽的风险无视所有屌丝基友的约定,
女神时间安排操作优先于屌丝。

处理方法

用两颗树来维护女神和屌丝的时间。女神邀约时先在屌丝树上找空闲(满足条件的最大连续)找不到再在女神树上找空闲(满足条件的最大连续)
女神操作染色时,要在两棵树上都进行染色,而屌丝操作只染一棵树。

题解代码

#include 
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E5 + 10;
struct node {
    int l, r;
    int fmax, lmax, rmax;
    int lazy;
    0代表没时间,1代表有时间。
}t1[N << 2], t2[N << 2]; // 基友, 女神
void pushdown(node& op, int lazy) {
	需要标记时使用
    op.fmax = lazy * (op.r - op.l + 1);
    直接把这个节点标注成了完全连续,进行区间覆盖成10
    op.lmax = op.fmax, op.rmax = op.fmax;
    经过fmax被赋值后,fmax变成了区间长度乘lazy状态,可以直接赋给lmax和fmax
    op.lazy = lazy;
}
void pushdown(node t[], int x) {
	需要访问子节点时使用。
    if (t[x].lazy == -1) return;
    pushdown(t[x << 1], t[x].lazy), pushdown(t[x << 1 | 1], t[x].lazy);
    t[x].lazy = -1;
}
一个输入tr[x],一个输入tr,用地址和引用来区分两个pushdown
void pushup(node& p, node& l, node& r) {
    p.fmax = max(max(l.fmax, r.fmax), l.rmax + r.lmax);
    p.lmax = l.lmax + (l.lmax == l.r - l.l + 1 ? r.lmax : 0);
    如果完全左子节点完全连续,就加上右子节点的从左往右最大连续,是一段连续的区间
    p.rmax = r.rmax + (r.rmax == r.r - r.l + 1 ? l.rmax : 0);
    如果右子节点完全连续,就加上左子节点的从左往右最大连续。
}
void pushup(node t[], int x) { pushup(t[x], t[x << 1], t[x << 1 | 1]); }
节点维护多个信息时写个pushup重载简化代码.
一个简化代码的操作,输入u变换成u<<1,u<<1|1的节点,传入函数引用里。
void build(int l, int r, int x = 1) {
    t1[x] = t2[x] = { l, r, 1, 1, 1, -1 };lazy初始化为-1其他为1.
    if (l == r) return;
    int mid = l + r >> 1;
    build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
    pushup(t1, x), pushup(t2, x);
}

int findleft(node t[], int c, int x = 1) { //找到当前树中最左侧有连续c空闲时间的左端点(起始点)
	不用担心搜不到结果,因为是用tr[1] 最上层节点的fmax确定过有这样一段连续时间才搜的
    if (t[x].l == t[x].r) return t[x].l;
    符合条件就return
    pushdown(t, x);
    if (t[x << 1].fmax >= c) return findleft(t, c, x << 1);
    dfs,把1的深度搜完,转战2
    if (t[x << 1].rmax + t[x << 1 | 1].lmax >= c) return t[x << 1].r - t[x << 1].rmax + 1;2的深度搜完,转战3
    搜到了立刻返回,
    return findleft(t, c, x << 1 | 1);3的深度搜完
}
维护最大连续,不需要query,
如果要求某个区间的最大连续的话要l==tr[u].l ,r == tr[u].r然后返回fmax
此题要找最左的符合要求的连续长度,如果有就在里面搜。要判断有没有,只需要看根节点的fmax是否符合要求即可,
void modify(node t[], int l, int r, int x = 1) { //修改某一课树的
    if (l <= t[x].l && r >= t[x].r) { pushdown(t[x], 0); return; }
    pushdown(t, x);
    int mid = t[x].l + t[x].r >> 1;
    if (l <= mid) modify(t, l, r, x << 1);
    if (r > mid) modify(t, l, r, x << 1 | 1);
    pushup(t, x);
}
void modify(int l, int r, int c, int x = 1) { //两棵树一起修改的
鸡血状态下会推掉所有邀约,所以要两棵树一起修改成1.
女神邀请时两颗树要一起修改成0,所以要从外部引入值。
    if (l <= t1[x].l && r >= t1[x].r) {
    两棵树的l,r是一样的,判一棵就行
        pushdown(t1[x], c), pushdown(t2[x], c);
        return;
    }
    pushdown(t1, x), pushdown(t2, x);
    int mid = t1[x].l + t1[x].r >> 1;
    if (l <= mid) modify(l, r, c, x << 1);
    if (r > mid) modify(l, r, c, x << 1 | 1);
    pushup(t1, x), pushup(t2, x);
}
int main()
{
    int T; cin >> T;
    rep(Case, T) {
        printf("Case %d:\n", Case);
        int n, m; scanf("%d %d", &n, &m);
        build(1, n);
        while (m--) {
            char s[10]; scanf("%s", s);
            if (*s == 'S') {
                int l, r; scanf("%d %d", &l, &r);
                modify(l, r, 1);
                赋值1,相当于一个清空的状态。
                printf("I am the hope of chinese chengxuyuan!!\n");
            }
            else {
                int x; scanf("%d", &x);
                if (*s == 'N') {
                    if (t1[1].fmax >= x) { //基友树有足够的时间
                        int l = findleft(t1, x);
                        modify(l, l + x - 1, 0);
                        赋值0,有约状态,
                        printf("%d,don't put my gezi\n", l);
                    }
                    else if (t2[1].fmax >= x){ //女神树有足够的时间
                        int l = findleft(t2, x);
                        modify(l, l + x - 1, 0);
                        printf("%d,don't put my gezi\n", l);
                    }
                    else printf("wait for me\n");
                }
                else if (*s == 'D') {
                    if (t1[1].fmax >= x) {
                        int l = findleft(t1, x);
                        modify(t1, l, l + x - 1);
                        printf("%d,let's fly\n", l);
                    }
                    else printf("fly with yourself\n");
                }
            }
        }
    }
    return 0;
}

经过模仿得到的acwing代码

#include 
#include 
#include 
#include 
using namespace std;
const int N = 1e5 + 10;
struct node{
	int l,r;
	int fmax,lmax,rmax;
	int lazy;
}t1[N<<2],t2[N<<2]; 
void pushdown(node &t,int lazy){
	t.fmax = lazy * (t.r - t.l + 1);
	t.lmax = t.rmax = t.fmax;
	t.lazy = lazy;
}
void pushdown(node t[],int u){
	if(t[u].lazy != -1){
		pushdown(t[u<<1],t[u].lazy),pushdown(t[u<<1|1],t[u].lazy);
		t[u].lazy = -1;
	}
}
void pushup(node&p,node&l,node&r){
	p.fmax = max(max(l.fmax,r.fmax),l.rmax + r.lmax);
	p.lmax = l.lmax + (l.lmax == l.r - l.l + 1 ? r.lmax : 0);
	p.rmax = r.rmax + (r.rmax == r.r - r.l + 1 ? l.rmax : 0);
}
void pushup(node t[],int u){
	pushup(t[u],t[u<<1],t[u<<1|1]);
}
void build(int u,int l,int r){
	t1[u] = t2[u] = {l,r,1,1,1,-1};
	if(l == r) return;
	int mid = l + r >> 1;
	build(u<<1,l,mid),build(u<<1|1,mid+1,r);
	pushup(t1,u),pushup(t2,u);
}


void modify(node tr[],int u,int l,int r){
	if(tr[u].l >= l && tr[u].r <= r){
		pushdown(tr[u],0);
		return;
	}
	pushdown(tr,u);
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid) modify(tr,u<<1,l,r);
	if(r > mid) modify(tr,u<<1|1,l,r);
	pushup(tr,u);
}
void modify(int u,int l,int r,int v){
	if(t1[u].l >= l && t1[u].r <=r ){
		pushdown(t1[u],v),pushdown(t2[u],v);
		return;
	}
	pushdown(t1,u),pushdown(t2,u);
	int mid = t1[u].l + t1[u].r >> 1;
	if(l <= mid) modify(u<<1,l,r,v);
	if(r > mid) modify(u<<1|1,l,r,v);
	pushup(t1,u),pushup(t2,u);
}
int main(){
	int t;
	cin >> t;
	for(int Case = 1;Case <= t;Case++){
		printf("Case %d:\n",Case);
		int  n,m;
		scanf("%d%d",&n,&m);
		build(1,1,n);
		while(m--){
			char s[10];
			scanf("%s",s);
			if(*s == 'S'){
				int l,r;
				scanf("%d%d",&l,&r);
				modify(1,l,r,1);
				printf("I am the hope of chinese chengxuyuan!!\n");
			}
			else{
				int x;
				scanf("%d",&x);
				if(*s == 'N'){
					if(t1[1].fmax >= x){
						int l = findleft(t1,1,x);
						modify(1,l,l+x-1,0);
						printf("%d,don't put my gezi\n", l);
					}
					else if(t2[1].fmax >= x){
						int l = findleft(t2,1,x);
						modify(1,l,l+x-1,0);
						printf("%d,don't put my gezi\n", l);
					}
                    else printf("wait for me\n");		
				}
				else if(*s == 'D'){
					if(t1[1].fmax >= x){
						int l = findleft(t1,1,x);
						modify(t1,1,l,l+x-1);
                        printf("%d,let's fly\n", l);
					}
                    else printf("fly with yourself\n");
				}
			}
		}
		
	}
	return 0;
}

套路

前提条件

给连续区间长度,求满足长度要求的连续区间端点。
######应对:
dfs,fmax,lmax,rmax,搜fmax,rmax+lmax,

int findleft(node tr[],int u,int v){
	if(tr[u].l == tr[u].r ) return tr[u].l;
	
	pushdown(tr,u);
	if(tr[u<<1].fmax >= v)return findleft(tr,u<<1,v);
	会递到第一个左子节点<v的位置,然后回归到最后一个左子节点<= v的位置,然后往下面的语句走。
	if(tr[u<<1].rmax +tr[u<<1|1].lmax >= v) return tr[u<<1].r - tr[u<<1].rmax + 1;
	中间的区间不符合要求,说明答案不在左子节点,就往右子节点递归。
	return findleft(tr,u<<1|1,v);
}

例题4最大连通区间维护,给点找连续区间长度

	找连续区间,用递归找,x点在的连续区间,长度大于n的连续区间。
	没有优先级,可以先找左子节点,也可以先找右子节点。
	和例题3的递归一样,x在左右子节点的合并区间或在叶子节点才返回具体的值。
	fmax不能用来确定x的位置,所以没有任何用,此题就没有使用fmax,只使用了
	lmax和rmax

套路:

前提条件:给点,找连续区间长度

应对,根据点与mid的值来确定在左右子节点,然后找

左rmax的左端点,
右lmax的右端点
看x在不在。在则返回左rmax+右lmax,
不在则继续搜左子节点(右子节点)
搜到叶子返回节点的lmax或rmax

int query(int u,int x){
	if(tr[u].l == tr[u].r) return tr[u].lmax;
	int mid = tr[u].l + tr[u].r >> 1;
	if(x <= mid){
		if(x>= tr[u<<1].r - tr[u<<1].rmax + 1) return tr[u<<1].rmax + tr[u<<1|1].lmax;
		return query(u<<1,x);
	}
	else{
		if(x<= tr[u<<1|1].l + tr[u<<1|1].lmax - 1) return tr[u<<1].rmax + tr[u<<1|1].lmax;
		return query(u<<1|1,x);   
	}
}

题解代码

#include 
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 5E4 + 10;
struct node {
    int l, r;
    int lmax, rmax; //左右最大连续长度
}t[N << 2];
void pushup(node& p, node& l, node& r) { //区间合并
    p.lmax = l.lmax + (l.lmax == l.r - l.l + 1 ? r.lmax : 0);
    p.rmax = r.rmax + (r.rmax == r.r - r.l + 1 ? l.rmax : 0);
}
void pushup(int x) { pushup(t[x], t[x << 1], t[x << 1 | 1]); }

void build(int l, int r, int x = 1) {
    t[x] = { l, r, 1, 1 };
    if (l == r) return;
    int mid = l + r >> 1;
    build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
    pushup(x);
}

void modify(int a, int c, int x = 1) {
    if (t[x].l == t[x].r) {
        t[x].lmax = t[x].rmax = c;
        return;
    }
    int mid = t[x].l + t[x].r >> 1;
    modify(a, c, x << 1 | (a > mid));
    pushup(x);
}

int ask(int a, int x = 1) {
    if (t[x].l == t[x].r) return t[x].lmax;

    int mid = t[x].l + t[x].r >> 1;
    if (a <= mid) { //表明a在左子树, 看看左子树右连续区间是否包含a点
        node& op = t[x << 1];
        if (a >= op.r - op.rmax + 1) return op.rmax + t[x << 1 | 1].lmax;
        询问a是否在从右往左最大连续的区间内,
        return ask(a, x << 1); //不包含a
    }
    else { //表明a在右子树, 看看右子树左连续区间是否包含a点
        node& op = t[x << 1 | 1];
        if (a <= op.l + op.lmax - 1) return op.lmax + t[x << 1].rmax;
        询问a是否在从左往右最大连续内,在则返回左右子节点的合并区间
        return ask(a, x << 1 | 1); //不包含a
        不在则继续搜
    }
}
int main()
{
    int n, m;
    while (~scanf("%d %d", &n, &m)) {
        stack<int> st; //记录最后被删除的点
        build(1, n);
        rep(i, m) {
            char s[2]; scanf("%s", s);
            if (s[0] == 'D') {
                int a; scanf("%d", &a);
                modify(a, 0);
                st.push(a);
            }
            else if (s[0] == 'Q') {
                int a; scanf("%d", &a);
                printf("%d\n", ask(a));
            }
            else modify(st.top(), 1), st.pop();
        }
    }
    return 0;
}


模仿后得到的acwing代码

#include 
#include 
#include 
#include 
#include 
using namespace std;
const int N = 5e4 + 10;
struct node{
	int l,r;
	int lmax,rmax;
	
}tr[N<<2];
void pushup(node &tr,node &l,node &r){
    tr.lmax = l.lmax + (l.lmax == l.r - l.l + 1 ? r.lmax : 0);
    tr.rmax = r.rmax + (r.rmax == r.r - r.l + 1 ? l.rmax : 0);
		 
}
void pushup(int u){
	pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r){
	tr[u] = {l,r,1,1};
	if(l == r) return;
	int mid = l + r >> 1;
	build(u<<1,l,mid),build(u<<1|1,mid+1,r);
	pushup(u);
}
int query(int u,int x){
	if(tr[u].l == tr[u].r) return tr[u].lmax;
	int mid = tr[u].l + tr[u].r >> 1;
	if(x <= mid){
		if(x>= tr[u<<1].r - tr[u<<1].rmax + 1) return tr[u<<1].rmax + tr[u<<1|1].lmax;
		 找到了直接返回值,因为第一次找到肯定是最接近根节点,最大的。
		return query(u<<1,x  );
		找连续区间,用递归找,x点在的连续区间,长度大于n的连续区间。
		没有优先级,可以先找左子节点,也可以先找右子节点。
		和例题3的递归一样,x在左右子节点的合并区间或在叶子节点才返回具体的值。
		fmax不能用来确定x的位置,所以没有任何用,此题就没有使用fmax,只使用了
		lmax和rmax
	}
	else{
		if(x<= tr[u<<1|1].l + tr[u<<1|1].lmax - 1) return tr[u<<1].rmax + tr[u<<1|1].lmax;
		return query(u<<1|1,x);   
	}
	
}
void modify(int u,int x,int v){
	if(tr[u].l == tr[u].r){
		tr[u].lmax = tr[u].rmax = v;
		return;
	}
	int mid = tr[u].l + tr[u].r >> 1;
	if(x <= mid) modify(u<<1,x,v);
	else modify(u<<1|1,x,v);
	pushup(u);  
}
int main()
{
    int n, m;
    while (~scanf("%d %d", &n, &m)) {
        stack<int> st; //记录最后被删除的点
        可以连续修复,所以用stack存摧毁的位置,先进后出,栈顶就是最新的一个元素。
        build(1,1, n);
        for(int i= 1;i <= m;i++) {
            char s[2]; scanf("%s", s);
            if (s[0] == 'D') {
                int a; scanf("%d", &a);
                modify(1,a, 0);
                st.push(a);
            }
            else if (s[0] == 'Q') {
                int a; scanf("%d", &a);
                printf("%d\n", query(1,a));
            }
            else modify(1,st.top(), 1), st.pop();
        }
    }
    return 0;
}

习题1最大连续区间维护,例题3的简化版:

题链:

Hotel
22ACM集训队-树状数组与线段树基础 - Virtual Judge (vjudge.net)

代码

#include 
#include 
#include 
#include 
using namespace std;
const int N = 5e4 + 10;
struct node{
	int l,    r;
	int fmax,lmax,rmax;
	int lazy;
}tr[N<<2];
void pushdown(node &tr,int lazy){
	tr.fmax = lazy * (tr.r - tr.l + 1);
	tr.lmax = tr.rmax = tr.fmax;
	tr.lazy = lazy;
}
void pushdown(int u){
	if(tr[u].lazy != -1){
		pushdown(tr[u<<1],tr[u].lazy) ,pushdown(tr[u<<1|1],tr[u].lazy);
		tr[u].lazy = -1;
	}
}
void pushup(node &tr,node &l,node &r){
	tr.fmax = max(max(l.fmax,r.fmax),l.rmax + r.lmax);
	tr.lmax = l.lmax + (l.lmax == l.r - l.l + 1 ? r.lmax : 0);
	tr.rmax = r.rmax + (r.rmax == r.r - r.l + 1 ? l.rmax : 0);
}
void pushup(int u){
	pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r){
	tr[u].l = l,tr[u].r = r,tr[u].fmax  = tr[u].lmax = tr[u].rmax = 1,tr[u].lazy = -1;
	if(l == r) return;
	int mid = l + r >> 1;
	build(u<<1,l,mid),build(u<<1|1,mid+1,r);
	pushup(u);
}
int findleft(int u,int v){
	if(tr[u].l == tr[u].r) return tr[u].l;
	pushdown(u);
	if(tr[u<<1].fmax >= v) return findleft(u<<1,v);
	if(tr[u<<1].rmax + tr[u<<1|1].lmax >= v) return tr[u<<1].r - tr[u<<1].rmax + 1;
	return findleft(u<<1|1,v);
}
void modify(int u,int l,int r,int v){
	if(tr[u].l >= l && tr[u].r <= r){
		pushdown(tr[u],v);
		return;
	}
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid) modify(u<<1,l,r,v);
	if(r > mid) modify(u<<1|1,l,r,v);
	pushup(u);
}
int main(){
	int n,m;
	cin >> n >> m;
	build(1,1,n);
	while(m--){
		int op;
		scanf("%d",&op);
		if(op == 1){
			int x;
			scanf("%d",&x);
			if(tr[1].fmax >= x) {
				int l = findleft(1,x);
				printf("%d\n",l);
				modify(1,l,l+x-1,0);
			}
			else {
				printf("0\n");
			}
			
		}
		else{
			int l,r;
			scanf("%d%d",&l,&r);
			modify(1,l,l+r-1,1);
		}
	}
	return 0;
}

习题2批量替换,例题1的变式

就只是根据操作变了个pushdown。
根节点tr[1].sum,查询树的总和

代码

#include 
#include 
#include 
#include 
using namespace std;
const int N = 1e5 + 10;
struct node{
	int l,r;
	int sum;
	int lazy;
}tr[N<<2];
void pushdown(node &tr,int lazy){
	tr.sum = lazy * (tr.r - tr.l + 1);
	tr.lazy = lazy;
}
void pushdown(int u){
	if(tr[u].lazy != -1){
		pushdown(tr[u<<1],tr[u].lazy),pushdown(tr[u<<1|1],tr[u].lazy);
		tr[u].lazy = -1;
	}
}
void pushup(int u){
	tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum;
}
void build(int u,int l,int r){
	tr[u] = {l,r,1,-1};
	if(l == r) return;
	int mid = l + r >> 1;
	build(u<<1,l,mid),build(u<<1|1,mid+1,r);
	pushup(u);
}
void modify(int u,int l,int r,int v){
	if(tr[u].l >= l && tr[u].r <= r){
		pushdown(tr[u],v);
		return;
	}
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid) modify(u<<1,l,r,v);
	if(r > mid) modify(u<<1|1,l,r,v);
	pushup(u);
}
int main(){
	int t;
	cin >> t;
	for(int Case = 1;Case <= t;Case++){
		int n,m;
		scanf("%d%d",&n,&m);
		build(1,1,n);
		while(m--){
			int l,r,v;
			scanf("%d%d%d",&l,&r,&v);
			modify(1,l,r,v);
		}
		
		printf("Case %d: The total value of the hook is %d.\n",Case,tr[1].sum);
	}
	return 0;
}

2.批量自适应修改

题链

Q - Hotel
22ACM集训队-树状数组与线段树基础 - Virtual Judge (vjudge.net)

卡壳点:

忘记build,递归的 tr[u].l == tr[u].r == 写成 =
区间修改,题目给的时l和len,以为是l和r导致错误
用l和len表示r错误,写成了l + len,应该是l + len -1 ;

代码

#include 
#include 
#include 
#include 
using namespace std;
const int N = 5e4 + 10;
struct node{
	int l,    r;
	int fmax,lmax,rmax;
	int lazy;
}tr[N<<2];
void pushdown(node &tr,int lazy){
	tr.fmax = lazy * (tr.r - tr.l + 1);
	tr.lmax = tr.rmax = tr.fmax;
	tr.lazy = lazy;
}
void pushdown(int u){
	if(tr[u].lazy != -1){
		pushdown(tr[u<<1],tr[u].lazy) ,pushdown(tr[u<<1|1],tr[u].lazy);
		tr[u].lazy = -1;
	}
}
void pushup(node &tr,node &l,node &r){
	tr.fmax = max(max(l.fmax,r.fmax),l.rmax + r.lmax);
	tr.lmax = l.lmax + (l.lmax == l.r - l.l + 1 ? r.lmax : 0);
	tr.rmax = r.rmax + (r.rmax == r.r - r.l + 1 ? l.rmax : 0);
}
void pushup(int u){
	pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r){
	tr[u].l = l,tr[u].r = r,tr[u].fmax  = tr[u].lmax = tr[u].rmax = 1,tr[u].lazy = -1;
	if(l == r) return;
	int mid = l + r >> 1;
	build(u<<1,l,mid),build(u<<1|1,mid+1,r);
	pushup(u);
}
int findleft(int u,int v){
	if(tr[u].l == tr[u].r) return tr[u].l;
	pushdown(u);
	if(tr[u<<1].fmax >= v) return findleft(u<<1,v);
	if(tr[u<<1].rmax + tr[u<<1|1].lmax >= v) return tr[u<<1].r - tr[u<<1].rmax + 1;
	return findleft(u<<1|1,v);
}
void modify(int u,int l,int r,int v){
	if(tr[u].l >= l && tr[u].r <= r){
		pushdown(tr[u],v);
		return;
	}
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid) modify(u<<1,l,r,v);
	if(r > mid) modify(u<<1|1,l,r,v);
	pushup(u);
}
int main(){
	int n,m;
	cin >> n >> m;
	build(1,1,n);
	while(m--){
		int op;
		scanf("%d",&op);
		if(op == 1){
			int x;
			scanf("%d",&x);
			if(tr[1].fmax >= x) {
				int l = findleft(1,x);
				printf("%d\n",l);
				modify(1,l,l+x-1,0);
			}
			else {
				printf("0\n");
			}
			
		}
		else{
			int l,r;
			scanf("%d%d",&l,&r);
			modify(1,l,l+r-1,1);
		}
	}
	return 0;
}

前提条件

是区间修改,区间查询,且修改操作的修改的值是根据具体节点储存的值而变化的,比如开根,幂,替换,乘除;
新做的题里发现替换可以直接批量赋值来实现,属于等值修改。


情景

对一个序列里的元素执行k次自适应操作,每次操作一个区间,然后询问区间内所有元素的值。
也有询问某个区间内所有值经过某种处理后的值。(此种问法是询问时用一个变量储存找到的值,经过处理后返回


例题1单种操作

Can you answer these queries?
22ACM集训队-树状数组与线段树基础 - Virtual Judge (vjudge.net)
主要就是把modify的递归条件改成了和传统query操作相同的有交集
复杂度比较高,需要一些剪枝

代码

#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
struct node{
	int l,r;
	ll sum;
}tr[N<<2];
ll w[N];
void pushup(int u){
	tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum;
}
void build(int u,int l,int r){
	tr[u] = {l,r,w[r]};
//	cout << w[r] << endl;
	if(l == r) return;
	int mid = l + r >> 1;
	build(u<<1,l,mid),build(u<<1|1,mid+1,r);
	pushup(u);
}
ll query(int u,int l,int r){
	if(tr[u].l >= l && tr[u].r <= r) {
		return tr[u].sum;
		
	}	
//		cout << tr[u].sum;
	ll res =0 ;
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid) res += query(u<<1,l,r);
	if(r > mid) res += query(u<<1|1,l,r);
	
	return res;
}
void modify(int u,int l,int r){
	
	if(tr[u].l == tr[u].r) tr[u].sum = sqrt(tr[u].sum);
	else{
		if(tr[u].sum == tr[u].r - tr[u].l + 1) return;
		int mid = tr[u].l + tr[u].r >> 1;
		if(l <= mid) modify(u<<1,l,r);
		if(r > mid) modify(u<<1|1,l,r);
		pushup(u);	
	}
	
}
	

int main()
{
    int T = 1;
    int n, m;
    while (cin >> n) {
        for(int i = 1;i <= n;i++) scanf("%lld", &w[i]);
        
        build(1,1, n);
        
        printf("Case #%d:\n", T++);
        scanf("%d", &m);
        while (m--) {
            int op, l, r; scanf("%d %d %d", &op, &l, &r);
            if (l > r) swap(l, r);
//            cout << l << " " << r << endl;
            if (op) printf("%lld\n", query(1,l, r));
            else modify(1,l, r);
        }
        printf("\n");
    }
    return 0;
}

例题2多种操作

Transformation HDU - 4578
22ACM集训队-树状数组与线段树基础 - Virtual Judge (vjudge.net)

题解代码

Transformation HDU - 4578 (线段树,审题很重要)_Soar-的博客-CSDN博客

#include
 
using namespace std;
#define lson i<<1,l,m
#define rson i<<1|1, m+1,r
const int mod = 10007;
const int maxn=1e5+10;
 
int x[maxn<<2],flag[maxn<<2];
 x是tr,flag是lazy
void pushup(int i,int l,int r)
{
    if(!flag[i<<1] || !flag[i<<1|1])左右子节点无值
        flag[i] = 0;
    else if(x[i<<1] != x[i<<1|1])左右子节点有值且不等
        flag[i] = 0;
    else flag[i]=1,x[i]=x[i<<1];左右子节点值相等
    所以这是一个记录懒标记的函数,如果左右子节点的值相同,就上传。
    
    通过用父节点的节点的值来代表子节点的值接受处理,降低复杂度
}
 
void pushdown(int i,int l,int r)
{
    if(flag[i])
    {
        flag[i<<1] = flag[i<<1|1] =1;
        x[i<<1] = x[i<<1|1] = x[i];
        flag[i]=0;
    }
    这是一个下传懒标记并处理懒标记的函数,如果有懒标记,说明这个节点是代表子节点接受处理的,所以直接将值下传到子节点,然后清除懒标记
}
 
void update(int ql,int qr,int p,int v,int i,int l,int r)
{
	妙:直接传入op,也就是p,根据p的值进行不同操作,减少了很多赘余的代码。
	我写时想的是写3个modify,也就是update,根据op不同,调用不同的modify,麻烦得很。
    if(ql<=l && qr>=r && flag[i])
    这里是有懒标记,且节点区间全都在需要处理的区间内,直接处理当前节点,然后pushdown,就可以实现区间处理
    {
        if(p==1)
            x[i] = (x[i]+v)%mod;
        else if(p==2)
            x[i] = (x[i]*v)%mod;
        else x[i] = v;
        修改当前节点值的话是不需要pushup的,因为pushup的操作是根据子节点的值来决定是否赋予当前节点一个懒标记,只修改当前节点值,代表当前节点已经是叶子节点,或者左右节点值相同,所以就算pushup了,懒标记还是会保持原有状态
        return;
    }
    pushdown(i,l,r);
 可能没有懒标记,会需要逐个单点修改,所以用两个if的原始query递归形式
    int m = (l+r)>>1;
    if(ql<=m) update(ql,qr,p,v,lson);
    if(qr>m) update(ql,qr,p,v,rson);
    进行子节点单点值修改操作后都需要pushup,来更新懒标记状态
    
    pushup(i,l,r);
}
int query(int ql,int qr,int num,int i,int l,int r)
l,r是当前节点的l,r
{
    if(flag[i] && ql<=l && qr>=r)
    {
        int ans=1;
        for(int j=0;j<num;j++)ans=(ans*x[i])%mod;//pow操作,每次pow取余,如果是10007的三次方就有可能爆int了,所以用循环来每次操作后取余
        ans=(ans*(r-l+1))%mod;
        return ans;
    }
 
    pushdown(i,l,r);
 
    int m = (l+r)>>1;
    int ans=0;
    if(ql<=m)ans+=query(ql,qr,num,lson);
    if(qr>m)ans+=query(ql,qr,num,rson);
    return ans%mod;
}
 
int main()
{
    int n,m;
    while(cin>>n>>m,n||m)
    {
        memset(flag,1,sizeof flag);
        memset(x,0,sizeof x);
        int p,x,y,v;
        while(m--)
        {
            scanf("%d%d%d%d",&p,&x,&y,&v);
            if(p>=1 && p<=3)update(x,y,p,v,1,1,n);
            else printf("%d\n",query(x,y,v,1,1,n));
        }
    }
}

经过模仿后得到的acwing版代码

#include 
#include 
#include 
#include 
using namespace std;
const int N = 1e5 + 10,mod = 10007;
struct node{
	int l,r;
	int sum;
	int lazy;
}tr[N<<2];
void pushup(int u){
	if(!tr[u<<1].lazy || !tr[u<<1|1].lazy) tr[u].lazy = 0;
	有一个子节点懒标记是0(当前节点的子节点的两个子节点的值不相等)则当前节点懒标记就变成0,由此可以推断出,懒标记的含义是表示当前节点的子树里所有节点的值 ,都相等,可以直接用当前节点的值来进行操作。 

	else if(tr[u<<1].sum != tr[u<<1|1].sum) tr[u].lazy = 0;
	else tr[u].lazy = 1,tr[u].sum = tr[u<<1].sum;
}
void pushdown(int u){
	if(tr[u].lazy){
		tr[u<<1].lazy = tr[u<<1|1].lazy = 1;
		tr[u<<1].sum = tr[u<<1|1].sum = tr[u].sum;
		tr[u].lazy = 0;
	}
}
void build(int u,int l,int r){
只有叶子节点才能初始化成lazy1
	if(l == r){
		tr[u] = {l,r,0,1};
		 return;
	}
	tr[u] = {l,r};
	int mid = l + r >> 1;
	build(u<<1,l,mid),build(u<<1|1,mid+1,r);
}
void modify(int u,int l,int r,int op,int v){
	if(tr[u].l >= l && tr[u].r <= r && tr[u].lazy){
		
		if(op == 1) tr[u].sum = (tr[u].sum + v) % mod;
		else if(op == 2) tr[u].sum = (tr[u].sum * v)%mod;
		else {
			tr[u].sum = v;	
		}		

		return;
	}
	pushdown(u);
	有懒标记要先处理,然后再运算。
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid) modify(u<<1,l,r,op,v);
	if(r > mid) modify(u<<1|1,l,r,op,v);
	pushup(u);
	
}
int query(int u,int l,int r,int v){
	if(tr[u].l  >= l && tr[u].r <= r && tr[u].lazy){
		int res = 1;
		for(int i = 0;i < v;i++) res = (res * tr[u].sum) % mod;
		res = res * (tr[u].r - tr[u].l + 1) % mod;
		return res;
	}
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1;
	int res = 0;
	if(l <= mid) res = (res + query(u<<1,l,r,v) )	%mod; 
	if(r > mid) res = (res + query(u<<1|1,l,r,v)) % mod;
	return res % mod;
}
int main(){
	int n,m;
	while(cin >> n >> m,n||m){
//		for(int i=0;i <= N << 2;i++) tr[i]= {0,0,0,0};	
		build(1,1,n);
		int op,l,r,v;
		while(m--){
			scanf("%d%d%d%d",&op,&l,&r,&v);
//			cout << op << " " << l << " " << r << " " << v << endl;
			if(op >=1 && op <= 3){
				modify(1,l,r,op,v);
			}
			else {
				printf("%d\n",query(1,l,r,v));
			}
		}
	}
	return  0;
}

注意

注意点就是非数组模拟节点的代码要用build初始化,然后叶子节点懒标记初始化为1,因为代表的含义是两个子节点值是否相等

懒标记区间自适应修改至少耗时2s,如果能操作简单,能用简单的语句来确定是否可以省略操作(如例题1tr[u].r - tr[u].l + 1) == tr[u].sum)判断区间内是否全为1而可以省略掉区间开方操作。

其他套路:

涉及到一个多组输入的套路
前提条件是没有明确组数,结束关键词的多组数据集输入
取反while(~scanf(“%d”,&n)
和while(scanf(“%d”,&n) != EOF)
还有while(cin >> n)三种形式

习题1开方单点剪枝1e5 + 2e5,常数n为6以内:

题链

花神游历各国
22ACM集训队-树状数组与线段树基础 - Virtual Judge (vjudge.net)


比较

与例题1是同一题型,但例题1的题解里没有用到fmax来维护区间最大值,而是直接用
tr[u].sum 与 tr[u].r - tr[u].l +1 是否相等来判断是否return。
例题1的做法仅限于元素最小是1的情况下,
习题1的元素最小为0,所以不能用这种做法。
于是归结出第一个剪枝套路

卡壳点:

没注意node内的maxn也要在modify内开方。
注意操作时除了l,r之外的属性基本都要处理
build初始化,注意叶子节点和根节点的初始化可能会不同。


代码

#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
struct node{
	int l,r;
	ll sum;
	int maxn;
}tr[N<<2];
ll w[N];
void pushup(int u){
	tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum;
	tr[u].maxn = max(tr[u<<1].maxn,tr[u<<1|1].maxn);
}
void build(int u,int l,int r){
	if(l == r){
		tr[u] = {l,r,w[r],w[r]};
		return;
	}tr[u] = {l,r,0,0 };
//	cout << w[r] << endl;
	
	int mid = l + r >> 1;
	build(u<<1,l,mid),build(u<<1|1,mid+1,r);
	pushup(u);
}
ll query(int u,int l,int r){
	if(tr[u].l >= l && tr[u].r <= r) {
		return tr[u].sum;
		
	}	
//		cout << tr[u].sum;
	ll res =0 ;
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid) res += query(u<<1,l,r);
	if(r > mid) res += query(u<<1|1,l,r);
	
	return res;
}
void modify(int u,int l,int r){
			if(tr[u].maxn <= 1) return;

	if(tr[u].l == tr[u].r) {
	tr[u].sum = sqrt(tr[u].sum);       tr[u].maxn = sqrt(tr[u].maxn);return;}
	
	
		int mid = tr[u].l + tr[u].r >> 1;
		if(l <= mid) modify(u<<1,l,r);
		if(r > mid) modify(u<<1|1,l,r);
		pushup(u);	
	
	
}
	

int main()
{
    int T = 1;
    int n, m;
    while (cin >> n) {
        for(int i = 1;i <= n;i++) scanf("%lld", &w[i]);
        
        build(1,1, n);
        
//        printf("Case #%d:\n", T++);
        scanf("%d", &m);
        while (m--) {
            int op, l, r; scanf("%d %d %d", &op, &l, &r);
//            cout << l << " " << r << endl;
            if (op==1) printf("%lld\n", query(1,l, r));
            else modify(1,l, r);
        }
        printf("\n");
    }
    return 0;
}

套路:

区间单点修改开平方剪枝:

前提条件,

对区间进行开平方,并需要动态维护区间和:

应对

节点中用fmax维护区间最值,modify时区间最值<=1就直接return。
如果节点值最小是1的话,直接通过判断tr[u].sum 与 tr[u].r - tr[u].l +1 是否相等可以确定是否需要剪枝。(区间值全为1)

3.区间染色

普通例题

题链

22ACM集训队-树状数组与线段树基础 - Virtual Judge (vjudge.net)

疑问

染过色后lazy被pushdown下移会不会有影响?
:modify经过lazy所在节点时,也就是lazy底下有节点被另外染色了此时lazy下移,这个节点没资格再代表其下的所有节点了。
判断是否颜色相同可以用个pushup,见批量自适应修改里的例题2多种操作里的pushup,但没什么必要。

代码

统计颜色种类及段数。
卡壳点:segment fault
用N作为下标访问元素时要养成写N-1的习惯,防止出现数组越界

#include 
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E4 + 10;
cou要开8011
int cou[N];
struct node {
    int l, r;
    int lazy;
}t[N << 2];
void pushdown(node& op, int lazy) { op.lazy = lazy; }
void pushdown(int x) {
    if (!t[x].lazy) return; 
    pushdown(t[x << 1], t[x].lazy), pushdown(t[x << 1 | 1], t[x].lazy);
    t[x].lazy = 0;
}

void build(int l, int r, int x = 1) {
	t[x] = { l, r, 0 };
    if (l == r) return;
    int mid = l + r >> 1;
    build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
}

void modify(int l, int r, int c, int x = 1) {
    if (l <= t[x].l && r >= t[x].r) { pushdown(t[x], c); return; }
    写错条件成叶子节点。
    pushdown(x);
    int mid = t[x].l + t[x].r >> 1;
    忘记/2
    if (l <= mid) modify(l, r, c, x << 1);
    if (r > mid) modify(l, r, c, x << 1 | 1);
    忘记变成x<<1|1
}

int last = 0; //表示上一次碰到的颜色, 记得当遍历叶子结点时没有颜色的时候也要记录.
void ask(int x = 1) {
	
    if (last != t[x].lazy) cou[t[x].lazy]++;
    判断是否间隔开
    if (t[x].lazy || t[x].l == t[x].r) { last = t[x].lazy; return; }
    当前节点lazy不为0,表示当前节点下的所有节点都经过区间修改,变成一样的颜色,不用再继续深入。
    //	有标记直接进上面的语句了,不需要pushdown 
    遍历整棵树
    ask(x << 1), ask(x << 1 | 1);
    先左后右保证last一定在t[x]的左边
}
int main()
{
    int n;
    while (~scanf("%d", &n)) {
        build(1, 8010); last = 0;  
        memset(cou, 0, sizeof cou);
        o1查询的优化。
        rep(i, n) {
            int l, r, c; scanf("%d %d %d", &l, &r, &c);
            modify(l + 1, r, c + 1); //这里是为了让建树区间下标从1开始, 涂色记录也从1开始
        }
        ask();
        rep(i, 8010) if (cou[i]) printf("%d %d\n", i - 1, cou[i]);
        printf("\n");
    }
    return 0;
}

离散化例题

套路:

lazy代替sum:
区间染色

前提条件:

题目不需要求区间长度,操作时各种值无优先级,在区间上互相进行区间覆盖操作,求最后的状态。

情景

操作之间存在覆盖,遮挡的影响

之前画的一些线段可能会被后面的一些线段所覆盖
给出每张海报所贴的范围li,ri(1<=li<=ri<=10000000) 。求出最后还能看见多少张海报。

例题2离散化,下标很大,值很小

我们看这组数据[1,10],[1,3],[6,10],很明显答案是3
但是离散化之后为[1,4],[1,2],[3,4],答案变成了2
为解决这种问题,我们可以在更新线段树的时候将区间从[l,r]变成[l,r-1],就将区间转化成了[1,3],[1,1],[3,3]这样的树
但是当我们遇到这样的数据[1,3],[1,1],[2,2],[3,3],就会导致区间更新时出错,我们可以将初始数据的r都加上1,就排除了li和ri相等的情况,如果没有这种情况,离散化后的区间也都是一样的
其实这道题数据很弱,不管这样的情况也能过(逃
正解https://www.luogu.com.cn/paste/vl2ora2z

卡壳点:

query return时,+号写成,
u<<1|1,写错成u<<1
modify部分
下标用错,用了1,n
modify的值写成了1,应该是有多个值,每张海报的值不一样。
build的时候由于v的下标0的值是无意义的边界,所以build的r要写成v.size()-1;

题解代码

#include 
#include 
#include 
#include 
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E5 + 10;
bool vis[N]; //标记是否已经出现过第i张海报
struct node {
   int l, r;
   int id; //id表示lazy标签, 也表示当前节点值. 
}t[N << 2];
vector<int> v; vector<pair<int, int> > area; //v为离散化数组, area存放海报区间
int find(int x) { return lower_bound(v.begin(), v.end(), x) - v.begin(); }
离散化用
void pushdown(node& op, int id) { op.id = id; }
void pushdown(int x) {
   if (!t[x].id) return;
   pushdown(t[x << 1], t[x].id), pushdown(t[x << 1 | 1], t[x].id);
   t[x].id = 0; 
}
//因为线段树内部不维护任何数值, 所以也可以省去pushup这一操作.

void build(int l, int r, int x = 1) {
   t[x].l = l,t[x].r = r,t[x].id = 0 ;//id: 特别的, 如果0也是染色的点, 那么应初始化为-1
   if (l == r) return;
   int mid = l + r >> 1;
   build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
}

void modify(int l, int r, int c, int x = 1) {
   if (l <= t[x].l && r >= t[x].r) { pushdown(t[x], c); return; }
   pushdown(x);
   int mid = t[x].l + t[x].r >> 1;
   if (l <= mid) modify(l, r, c, x << 1);
   if (r > mid) modify(l, r, c, x << 1 | 1);
}

int ask(int x = 1) { 
   if (t[x].id) { //当前子树均为同一值, 没必要再递归下去了
   有标记,染过色
       if (vis[t[x].id]) return 0;
       如果出现过返回0
       return vis[t[x].id] = 1;
       没出现过直接返回st赋值语句。
   }
   if (t[x].l == t[x].r) return 0; //到叶子结点一定要结束递归
   叶子节点返回0.
   return ask(x << 1) + ask(x << 1 | 1);
   返回两个节点相加的值。
}

int main()
{
   int T; cin >> T; 
   while (T--) {
       v.clear(); v.push_back(-0x3f3f3f3f); //这里是为了离散化下标从1开始
       area.clear();
       int n; scanf("%d", &n);
       rep(i, n) {
           vis[i] = 0; //顺带初始化vis
           int l, r; scanf("%d %d", &l, &r);
           r++; 
           v.push_back(l), v.push_back(r);
           area.push_back(make_pair(l,r));
       }
       
       /* 离散化部分 */
       sort(v.begin(), v.end()); v.erase(unique(v.begin(), v.end()), v.end());
       rep(i, n) if (v[i] - v[i - 1] != 1) v.push_back(v[i] - 1); //记为*
       sort(v.begin(), v.end());
       
       build(1, v.size() - 1); //因为我的v数组有个-INF, 所以实际的建树大小应为v.size()-1
       
       for (int i = 0; i < n; ++i) {
           int l = area[i].first, r = area[i].second;
           l = find(l), r = find(r);
           modify(l, r-1, i + 1); //个人习惯编号从1开始
       }
       printf("%d\n", ask());
   }
   return 0;
}

经过模仿得到的acwing代码

#include 
#include 
#include 
#include 
using namespace std;
typedef pair<int,int>PII;
const int N = 1e4 + 10;
bool st[N<<1];
struct node{
	int l,r;
	int lazy;
}tr[N<<3];

vector<int> v; vector<PII > area;
int find(int u) {
	return lower_bound(v.begin(),v.end(),u) - v.begin();
}
void pushdown(node &tr,int lazy){
	tr.lazy = lazy;
}
void pushdown(int u){
	if(tr[u].lazy){
	
		pushdown(tr[u<<1],tr[u].lazy),pushdown(tr[u<<1|1],tr[u].lazy);
		tr[u].lazy = 0;
	}
}
void build(int u,int l,int r){
	tr[u].l = l,tr[u].r = r,tr[u].lazy = 0;
	if(l == r) return;
	int mid = l + r >> 1;
	build(u<<1,l,mid),build(u<<1|1,mid+1,r);
}
void modify(int u,int l,int r,int v){
	if(tr[u].l >= l && tr[u].r <= r) {
		pushdown(tr[u],v);
		return;
	}
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid) modify(u<<1,l,r,v);
	if(r > mid) modify(u<<1|1,l,r,v);
}
int query(int u){
	if(tr[u].lazy){
		if(st[tr[u].lazy])return 0;
		return st[tr[u].lazy] = 1;
	}
	if(tr[u].l == tr[u].r ) return 0;
	return query(u<<1) + query(u<<1|1);
}
int main(){
	int T;
	cin >> T;
	while(T--){
		v.clear();
		//初始化
		v.push_back(-0x3f3f3f3f);
		vector下标处理, 从1开始,设置-0x3f3f3f3f,可以在离散化二分中当作一个边界。
		area.clear();
		memset(st,0,sizeof st);
		//初始化
		int n;
		scanf("%d",&n);
	
		for(int i = 1;i <= n;i++){
			int l,r;
			scanf("%d%d",&l,&r);
			r++;
			区间染色离散化处理
			v.push_back(l),v.push_back(r);
			因为后面会排序,所以直接push_back进离散化vector就行。
			area.push_back(make_pair(l,r));
			c++98只能用make_pair,不能{l,r};
		}
		//
		sort(v.begin(),v.end());v.erase(unique(v.begin(),v.end()),v.end());
		离散化去重
		for(int i = 1;i <= n;i++) if(v[i] - v[i-1] != 1) v.push_back(v[i] - 1);
		获取离散化后的最大边界,用于建树:如果不相邻的话就要多加一个与v[i]相邻的元素
		sort(v.begin(),v.end());
		//
		v离散化。
		build(1,1,v.size()-1);
		for(int i = 0;i < n;i++){
			int l = area[i].first,r = area[i].second;
			l = find(l),r = find(r);
			find函数,把原始的l,r数据通过二分查找,映射到离散化后的l,r上。
			modify(1,l,r-1,i+1);
			
		}
		printf("%d\n",query(1));
	}
	return 0;
}


套路:

区间染色query查询:

前提:

区间染色问题的query

应对
如果是查颜色种类的话:
int query(int u){
	if(tr[u].lazy){
		if(st[tr[u].lazy]) return 0;
		return st[tr[u].lazy] = 1;
	}
	if(tr[u].l == tr[u].r ) return 0;
	return query(u<<1) + query(u<<1|1);
}
如果是查不同颜色和颜色对应的段数:

用cnt数组维护


int cnt[N];
int last = 0;
void query(int u){
	if(last != tr[u].lazy) cnt[tr[u].lazy]++;
	if(tr[u].lazy || tr[u].l == tr[u].r) {
		last = tr[u].lazy;
		return 0;
	}
//	有标记直接进上面的语句了,不需要pushdown 
	query(u<<1),query(u<<1|1);
}

区间染色的l,r离散化

前提:l,r的值特别大以至于没法开出对应的树
应对:
vector<int> v;vector<PII > area;
开两个vector,一个离散化处理,一个存原始

int find(int u){return lower_bound(v.begin(),v.end(),u) - v.begin();}
二分查找函数,找到第一个大于等于u的元素,原始l,r可以通过这个find函数映射为离散l,r

v.clear(),area.clear();
v.push_back(-0x3f3f3f3f);
让v下标改为1至n

for(int i = 1;i <= n;i++){
	int l,r;
	scanf("%d%d",&l,&r);
	r++;
	v.push_back(l),v.push_back(r);
	area.push_back(make_pair(l,r));
}
读入信息,线段树区间染色离散化的r要先++,操作时再-1抵消影响

sort(v.begin(),v.end());v.erase(unique(v.begin(),v.end()),v.end());
for(int i = 1;i <= n;i++) if(v[i]  - v[i-1] != 1) v.push_back(v[i]-1);
sort(v.begin(),v.end());
离散化操作

for(int i = 0;i < n;i++){
	int l = find(area[i].first),r = find(area[i].second);
	modify(1,l,r-1,i+1);
}
操作时通过下标0到n-1拿出存原始l,r的vector里的元素
然后经过find映射成离散化l,r
modify操作时r-1

区别

区间等值修改里,
lazy存的是子树中所有节点要修改的值或要修改成的值,根据题意决定初始化的值,如例题1中批量+v操作,初始化为0,例题2批量替换,初始化为v的定义域之外的值。
sum存的是子树所有节点sum的总和 ,一般初始化为0.
是一个修改时给节点打上lazy,遇到lazy时解开lazy的过程。

区间自适应修改里,
lazy作为一个bool变量,存的是当前节点是否能代表子树接受修改,根据题意决定初始化的值
sum只有在lazy为1时有实际意义,
存的是这颗所有节点的值都相同的子树里的这个相同的值
修改值时,直接修改节点的值

树状数组

lowbit证明

树状数组详解 - Xenny - 博客园 (cnblogs.com)

单点修改

例题1求区间内操作数量

题链

22ACM集训队-树状数组与线段树基础 - Virtual Judge (vjudge.net)

卡壳点:

看了半天,原来不是求区间前缀和,而是求区间内进行了多少次操作……
怪不得题解看着很怪

套路:

前提:

取区间内操作数量

情景:

学校决定在某个时刻在某一段种上一种树,保证任一时刻不会出现两段相同种类的树,现有两种操作:

  • K=1,读入 l,r 表示在 l 到 r 之间种上一种树,每次操作种的树的种类都不同;
  • K=2,读入 l,r 表示询问 l 到 r 之间有多少种树。
应对:

用一个括号序列的思路来求解
比线段树更优,更简单,所以不用线段树。
![[Pasted image 20230413173234.png]]
统计10之前有多少个左括号,再统计3之前以后多少个右括号,3之前的右括号肯定可以和10里面的某些左括号(匹配,且在3之前,说明这对括号对应的操作没有覆盖3到10
所以覆盖了3到10的括号就是10之前有多少个左括号减去3之前的右括号

用两个树状数组,一个维护左括号,一个维护右括号,单点修改就行了。

代码

#include 
#include 
#include 
#include 
using namespace std;
const int N = 5e4 + 10;
int tr1[N],tr2[N];
int lowbit(int x){
	return x & -x;
}
void add(int tr[],int x){
	for(int i = x;i <= N;i+=lowbit(i)) tr[i] ++;
}
int query(int tr[],int x){
	int res = 0;
	for(int i= x;i;i-=lowbit(i)) res += tr[i];
	return res;
}
int main(){
	int n,m;
	cin >> n >> m;
	for(int i = 1;i <= m;i++){
		int op,l,r;
		scanf("%d%d%d",&op,&l,&r);
		if(op == 1)add(tr1,l),add(tr2,r);
		else printf("%d\n",query(tr1,r)- query(tr2,l-1));//左括号-右括号 
	}
	return 0;
}

例题2树状数组模拟哈希

题链

22ACM集训队-树状数组与线段树基础 - Virtual Judge (vjudge.net)

卡壳点:

树状数组套路不熟悉,query里res没有初始化为0
没有看出来是树状数组模拟哈希,套路不熟悉。
没有注意到没有给出操作数量的范围,没开long long导致wa。

同类题目

( 树状数组哈希前缀和)小朋友排队

AcWing 1215. 小朋友排队 - AcWing

[AcWing 1215. 小朋友排队 - AcWing](https://www.acwing.com/activity/content/problem/content/1722/)
#### 我的思路(疯狂踩陷阱)
// 1e5,1e6
// ,表示小朋友的不高兴程度和的最小值。
// 开始的时候,所有小朋友的不高兴程度都是  0
//  。

// 如果某个小朋友第一次被要求交换,则他的不高兴程度增加  1
//  ,如果第二次要求他交换,则他的不高兴程度增加  2
//  (即不高兴程度为  3
//  ),依次类推。当要求某个小朋友第  k
//   次交换时,他的不高兴程度增加  k
//  。
//  n = (1 + cnt)*cnt / 2;
 
**//  相邻交换的最优解是冒泡排序,要求排序次数**
//  每次交换,一个数前面比他大的数量-1,或在后面比他小的数-1,所以交换次数就是在前面且> x,在后面且< x的数字数量,**哈希后求前缀和**可以实现这个操作,**动态求前缀和适合用树状数组,用树状数组模拟hash求前缀和后缀和**,然后等查数列求和求每一项


***
#### 题解思路
// 先前总结过一个 求数组中有多少个数小于等于的套路
// 变化一下,
// 求数组中前面有多少个数小于等于x,只要遍历数组,边哈希边前缀和(l-1,x)
// 求数组中后面有多少个数小于等于x,只要逆序遍历数组,边哈希边前缀和(l-1,x)
// 求数组中前面有多少个数大于等于x,只要遍历数组,边哈希边前缀和(x-1,r)
// 求数组中后面有多少个数大于等于x,只要逆序遍历数组,边哈希边前缀和(x-1,r)
// 单纯小于或大于x,则x变为x-1或x+1,
// 检查数据范围时要把计算式全部看一遍,看看会不会爆范围。

***// 检查数据范围时看到数据包含0或负数,取模时要打起十二分精神,看看有没有踩到陷阱***

***

### 套路:
哈希前缀和
前提条件

	序列,元素关系。序列中大于x的数量,小于x的数量,或是前面大于x的数量,后面大于x的元素数量
应对

	要一个哈希数组,一个保存结果的数组,可能要一个原数组
	求数组中前面有多少个数小于等于x,只要遍历数组,边哈希边前缀和(l-1,x)
	求数组中后面有多少个数小于等于x,只要逆序遍历数组,边哈希边前缀和(l-1,x)
	求数组中前面有多少个数大于等于x,只要遍历数组,边哈希边前缀和(x-1,r)
	求数组中后面有多少个数大于等于x,只要逆序遍历数组,边哈希边前缀和(x-1,r)
	严格小于或大于x,则x变为x-1或x+1,

	发现要求前面时,顺序遍历数组,后面时,倒序遍历数组
	
	 检查数据范围时要把计算式全部看一遍,看看会不会爆数据。
	 如果序列元素会改变,需要动态维护序列,要用树状数组或线段树来模拟哈希
用树状数组模拟哈希的重点是
重点是要满足边哈希,边求哈希前缀和

贪心策略
前提条件

	相邻交换序列元素:
 应对
 	
	最优策略是冒泡排序,每个元素会被交换的次数等于
	在前面且> x,在后面且< x的数字数量之和
(树状数组,线段树)(数组模拟哈希)(解题步骤)acwing数星星

https://www.acwing.com/problem/content/1267/

### 题链
https://www.acwing.com/problem/content/1267/
~~没买课的点不开,耗子尾汁~~ 
文末放图片

### 解决问题先看本质,找数据范围与输出
>// 输出格式
// N行,每行一个整数,分别是 0级,1级,2级,……,N−1级的星星的数目。
// 数据范围
// 1≤N≤15000
// ,
// 0≤x,y≤32000
**范围不是很特殊,没有太多信息**
### // 带着疑惑去看题干,轻松抓重点
==// 级是什么,数量如何统计:==

**在题干中发现:**
>// 如果一个星星的左下方(包含正左和正下)有 k颗星星,就说这颗星星是 k级的。

**//  因为涉及到坐标以及求和,到这里可以想到大概是用一个二维前缀和,但是二维前缀和要开的数组太大了,用不了,再看有没有其他条件,
 //  结果发现还真有:**

>//  **不会有星星重叠。星星按 y坐标增序给出,y坐标相同的按 x坐标增序给出。**

//  给出的点的y是不严格递增的,且y相同时,x是递增的这意味着,前面的点永远在后面的点的下方,

由此题目就变成按顺序看星星的话可以只看x坐标来判断前面的星星是否要计入后面的点的数量。

数量的计算就可以变成计算哈希统计x坐标各种情况的出现次数,然后计算1~x的前缀和。

//  因为涉及到了数组单点修改以及求和操作,所以可以用一个树状数组或线段树来模拟哈希维护数据

//观察样例发现星星计数时是不计自身的,所以先求树状数组里统计的前缀和,再修改树状数组里的值
// 统计数量级又要统计各种数量的出现情况,又要一层哈希

// 第一颗星星的左下方不可能有星星,且计数时不计自身,必然有0级的星星最后遍历哈希0到n-1

### 套路:
###### 树状数组模拟哈希
前提条件

	需要动态维护序列元素大小以及前缀和。

应对:

	求[l,r] 前缀和,用query(r) - query(l - 1)
	
###### 哈希前缀和
前提条件

	序列,元素关系。序列中大于x的数量,小于x的数量,或是前面大于x的数量,后面大于x的元素数量
应对

	求数组中前面有多少个数小于等于x,只要遍历数组,边哈希边前缀和(l-1,x)
	求数组中后面有多少个数小于等于x,只要逆序遍历数组,边哈希边前缀和(l-1,x)
	求数组中前面有多少个数大于等于x,只要遍历数组,边哈希边前缀和(x-1,r)
	求数组中后面有多少个数大于等于x,只要逆序遍历数组,边哈希边前缀和(x-1,r)
	严格小于或大于x,则x变为x-1或x+1,
	
	 检查数据范围时要把计算式全部看一遍,看看会不会爆数据。
	 如果序列元素会改变,需要动态维护序列,要用树状数组或线段树来模拟哈希
---

### 代码:
```cpp
#include 
#include 
#include 
#include 
using namespace std;
const int N = 4e4;
using namespace std;
int tr[N],w[N],f[N];
int lowbit(int x){
    return x & -x;
}
void add(int x,int v){
    for(int i = x;i <= N;i+=lowbit(i)) tr[i] += v;
}
int query(int x){
    int res = 0;
    for(int i = x;i ;i -=lowbit(i)) res += tr[i];
    return res;
}
int main(){
    int n;
    int x,y;
    cin >> n;
    for(int i = 0;i < n;i++){
        scanf("%d%d",&x,&y);
        x++;
        f[query(x)] ++;
        add(x,1);
    }
    for(int i  = 0;i < n;i++){
        printf("%d\n",f[i]);
    }
    return 0;
}

题3树状数组区间修改,用不到,暂时不管

`则A[1]+A[2]+…+A[n]

``= (D[1]) + (D[1]+D[2]) + … + (D[1]+D[2]+…+D[n])

``= n*D[1] + (n-1)*D[2] +… +D[n]

``= n * (D[1]+D[2]+…+D[n]) - (0D[1]+1D[2]+…+(n-1)*D[n])

`1到n的前缀和等于= n * (D[1]+D[2]+…+D[n]) - (0D[1]+1D[2]+…+(n-1)*D[n])

`所以上式可以变为∑ni = 1A[i] = n∑ni = 1D[i] - ∑ni = 1( D[i](i-1) );

`所以是需要维护tr1[i] = iD[i],tr2[i] = D[i](i-1);

1 int n,m;
 2 int a[50005] = {0},c[50005]; //对应原数组和树状数组
 3 
 4 int lowbit(int x){
 5     return x&(-x);
 6 }
 7 
 8 void updata(int i,int k){    //在i位置加上k
 9     while(i <= n){
10         c[i] += k;
11         i += lowbit(i);
12     }
13 }
14 
15 int getsum(int i){        //求D[1 - i]的和,即A[i]值
16     int res = 0;
17     while(i > 0){
18         res += c[i];
19         i -= lowbit(i);
20     }
21     return res;
22 }
23 
24 int main(){
25     cin>>n;27     for(int i = 1; i <= n; i++){
28         cin>>a[i];
29         updata(i,a[i] - a[i-1]);   //输入初值的时候,也相当于更新了值
31     }
32     
33     //[x,y]区间内加上k
34     updata(x,k);    //A[x] - A[x-1]增加k
35     updata(y+1,-k);        //A[y+1] - A[y]减少k
36     
37     //查询i位置的值
38     int sum = getsum(i);
39 
40     return 0;
41 }

模仿

#include 
#include 
#include 
#include 
using namespace std;
const int N = 1e4 + 10;
int f[N],tr1[N],tr2[N];
int lowbit(int x){
	return x & -x;
}
void add(int x,int v){
	for(int i = x;i <= N;i+=lowbit(i)){
		tr1[i] += v;
		tr2[i] += v * (x - 1);
	}
}
int query(int x){
	int res = 0;
	for(int i = x;i;i-=lowbit(i)) res += x * tr1[i] - tr2[i];
	return res;
}
int main(){
	int n,m;
	cin >> n >> m;
	for(int i = 1;i <= m;i++){
		int op,l,r;
		scanf("%d%d%d",&op,&l,&r);
		if(op == 1) add(l,1),add(r + 1,-1);
		else printf("%d\n",query(r) - query(l-1));
	}
	return 0;
}

你可能感兴趣的:(树状数组与线段树,c++,开发语言)