25五一线段树专题

0 前言

之前写过线段树的基本原理,当时是想顺便写完线段树应用的,后来因为时间问题没写完。

这是链接

这次正好zhx讲线段树。

原理我不放了,那篇写的我认为还是比较详细的,这里就把代码放一下吧。

原因是那篇的代码来自于wmy学长和《深入浅出进阶篇》,而这次来源于zhx。

他自称是最快的线段树板子。

不过事实的确如此。

非常方便简洁的代码。

1 P3372 【模板】线段树 1:线段树原理及实现

1.1 题目简述

略。

1.2 思路简述(线段树原理)

请见这篇

这里略。

1.3 代码实现

#include <bits/stdc++.h>
using namespace std;

#define int long long
#define root 1, n, 1
#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1 | 1

const int N = 1e5 + 5;

struct node {
    int sum;
    int siz;
    int add;

    node() {
        sum = 0;
        siz = 0;
        add = 0;
    }
    void init(int v) {
        sum = v;
        siz = 1;
    }
} z[N << 2];

node operator+(const node &l, const node &r) {
    node res;
    res.sum = l.sum + r.sum;
    res.siz = l.siz + r.siz;
    return res;
}

void color(int l, int r, int rt, int v) {  // 打标记
    z[rt].add += v;
    z[rt].sum += v * z[rt].siz;
}

void pushcol(int l, int r, int rt) {  // 标记下放
    if (z[rt].add == 0)               // 可有可无,有了会慢一点
        return;
    int mid = (l + r) >> 1;
    color(lson, z[rt].add);
    color(rson, z[rt].add);
    z[rt].add = 0;
}

int n, m;
int a[N];

void build(int l, int r, int rt) {  // 建树
    if (l == r) {
        z[rt].init(a[l]);
        return;
    }
    int mid = (l + r) >> 1;
    build(lson);
    build(rson);
    z[rt] = z[rt << 1] + z[rt << 1 | 1];
}

node query(int l, int r, int rt, int nowl, int nowr) {  // 区间查询
    // nowl nowr 代表询问的区间
    // l r rt 描述了一个线段树节点
    if (nowl <= l && r <= nowr)
        return z[rt];
    pushcol(l, r, rt);
    int mid = (l + r) >> 1;
    if (nowl <= mid) {
        if (mid < nowr)
            return query(lson, nowl, nowr) + query(rson, nowl, nowr);
        else
            return query(lson, nowl, nowr);
    } else
        return query(rson, nowl, nowr);
}

void modify(int l, int r, int rt, int nowl, int nowr, int v) {
    if (nowl <= l && r <= nowr) {
        color(l, r, rt, v);  // 打懒标记
        return;
    }
    pushcol(l, r, rt);
    int mid = (l + r) >> 1;
    if (nowl <= mid)
        modify(lson, nowl, nowr, v);
    if (mid < nowr)
        modify(rson, nowl, nowr, v);
    z[rt] = z[rt << 1] + z[rt << 1 | 1];
}

signed main() {
    cin.tie(0)->sync_with_stdio(0);

    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];

    build(root);

    cin >> m;
    for (int i = 1; i <= m; i++) {
        int opt;
        cin >> opt;
        if (opt == 2) {  // 询问
            int l, r;
            cin >> l >> r;
            cout << query(root, l, r).sum << '\n';
        } else {
            int l, r, v;
            cin >> l >> r >> v;
            modify(root, l, r, v);
        }
    }

    return 0;
}

2 线段树拓展

2.1 Problem 1

2.1.1 问题简述

已知一个数列 \(\{a_i\}\),你需要进行下面两种操作:

  1. 将某区间每一个数加上 \(k\)

  2. 求出某区间每一个数的和。

  3. 求出某区间的最大值和最小值。

2.1.2 思路简述

多维护一个最大值和一个最小值。

2.1.3 代码实现

这里不展示主函数。下同。

#define int long long
#define root 1, n, 1
#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1 | 1

const int N = 1e5 + 5;

struct node {
    int sum;
    int siz;
    int add;
    int minv;  //
    int maxv;  //
    node() {
        sum = 0;
        siz = 0;
        add = 0;
        minv = 1e18;
        maxv = -1e18;
    }
    void init(int v) {
        sum = v;
        minv = v;  //
        maxv = v;  //
        siz = 1;
    }
} z[N << 2];

node operator+(const node &l, const node &r) {
    node res;
    res.sum = l.sum + r.sum;
    res.siz = l.siz + r.siz;
    res.minv = min(l.minv, r.minv);  //
    res.maxv = max(l.maxv, r.maxv);  //
    return res;
}

void color(int l, int r, int rt, int v) {
    z[rt].add += v;
    z[rt].sum += v * z[rt].siz;
    z[rt].minv += v;  //
    z[rt].maxv += v;  //
}

void pushcol(int l, int r, int rt) {
    if (z[rt].add == 0)
        return;
    int mid = (l + r) >> 1;
    color(lson, z[rt].add);
    color(rson, z[rt].add);
    z[rt].add = 0;
}

int n, m;
int a[N];

void build(int l, int r, int rt) {
    if (l == r) {
        z[rt].init(a[l]);
        return;
    }
    int mid = (l + r) >> 1;
    build(lson);
    build(rson);
    z[rt] = z[rt << 1] + z[rt << 1 | 1];
}

node query(int l, int r, int rt, int nowl, int nowr) {
    if (nowl <= l && r <= nowr)
        return z[rt];
    pushcol(l, r, rt);
    int mid = (l + r) >> 1;
    if (nowl <= mid) {
        if (mid < nowr)
            return query(lson, nowl, nowr) + query(rson, nowl, nowr);
        else
            return query(lson, nowl, nowr);
    } else
        return query(rson, nowl, nowr);
}

void modify(int l, int r, int rt, int nowl, int nowr, int v) {
    if (nowl <= l && r <= nowr) {
        color(l, r, rt, v);
        return;
    }
    pushcol(l, r, rt);
    int mid = (l + r) >> 1;
    if (nowl <= mid)
        modify(lson, nowl, nowr, v);
    if (mid < nowr)
        modify(rson, nowl, nowr, v);
    z[rt] = z[rt << 1] + z[rt << 1 | 1];
}

2.2 Problem 2

2.2.1 题目简述

已知一个数列 \(\{a_i\}\),你需要进行下面两种操作:

  1. 将某区间每一个数加上 \(k\)
  2. 给出 \(l\)\(r\),询问 \(|a_l-a_{l+1}|+|a_{l+1}-a_{l+2}|+ \dots + |a_{r-1}-a_r|\) 的值。

2.2.2 思路简述

维护每个区间的左右端点,每次合并的时候注意加上左儿子右端点与右儿子左端点的差的绝对值。

2.2.3 代码实现

#define int long long
#define root 1, n, 1
#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1 | 1

const int N = 1e5 + 5;

struct node {
    int sum;
    int siz;
    int add;
    int lv;  //
    int rv;  //
    node() {
        sum = 0;
        siz = 0;
        add = 0;
    }
    void init(int v) {
        sum = v;
        lv = rv = v;  //
        siz = 1;
    }
} z[N << 2];

node operator+(const node &l, const node &r) {
    node res;
    res.sum = l.sum + r.sum + abs(l.rv - r.lv);
    res.siz = l.siz + r.siz;
    res.lv = l.lv;  //
    res.rv = r.rv;  //
    return res;
}

void color(int l, int r, int rt, int v) {
    z[rt].add += v;
    z[rt].lv += v;  //
    z[rt].rv += v;  //
}

void pushcol(int l, int r, int rt) {
    if (z[rt].add == 0)
        return;
    int mid = (l + r) >> 1;
    color(lson, z[rt].add);
    color(rson, z[rt].add);
    z[rt].add = 0;
}

int n, m;
int a[N];

void build(int l, int r, int rt) {
    if (l == r) {
        z[rt].init(a[l]);
        return;
    }
    int mid = (l + r) >> 1;
    build(lson);
    build(rson);
    z[rt] = z[rt << 1] + z[rt << 1 | 1];
}

node query(int l, int r, int rt, int nowl, int nowr) {
    if (nowl <= l && r <= nowr)
        return z[rt];
    pushcol(l, r, rt);
    int mid = (l + r) >> 1;
    if (nowl <= mid) {
        if (mid < nowr)
            return query(lson, nowl, nowr) + query(rson, nowl, nowr);
        else
            return query(lson, nowl, nowr);
    } else
        return query(rson, nowl, nowr);
}

void modify(int l, int r, int rt, int nowl, int nowr, int v) {
    if (nowl <= l && r <= nowr) {
        color(l, r, rt, v);
        return;
    }
    pushcol(l, r, rt);
    int mid = (l + r) >> 1;
    if (nowl <= mid)
        modify(lson, nowl, nowr, v);
    if (mid < nowr)
        modify(rson, nowl, nowr, v);
    z[rt] = z[rt << 1] + z[rt << 1 | 1];
}

2.3 Problem 3

2.3.1 题目简述

已知一个数列 \(\{a_i\}\),你需要进行下面两种操作:

  1. 将某区间每一个数加上 \(k\)
  2. 给出 \(l\)\(r\),询问 \(a_l^2+a_{l+1}^2+ \dots +a_r^2\) 的值。

2.3.2 思路简述

多维护一个 \(sum_2\) 表示平方和。

2.3.3 代码实现

#define int long long
#define root 1, n, 1
#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1 | 1

const int N = 1e5 + 5;

struct node {
    int sum;
    int siz;
    int add;
    int sum2;
    node() {
        sum = 0;
        siz = 0;
        add = 0;
    }
    void init(int v) {
        sum = v;
        sum2 = v * v;
        siz = 1;
    }
} z[N << 2];

node operator+(const node &l, const node &r) {
    node res;
    res.sum = l.sum + r.sum;
    res.sum2 = l.sum2 + r.sum2;
    res.siz = l.siz + r.siz;
    return res;
}

void color(int l, int r, int rt, int v) {
    z[rt].add += v;
    z[rt].sum2 += 2 * v * z[rt].sum + z[rt].siz * v * v;
    z[rt].sum += v * z[rt].siz;
}

void pushcol(int l, int r, int rt) {
    if (z[rt].add == 0)
        return;
    int mid = (l + r) >> 1;
    color(lson, z[rt].add);
    color(rson, z[rt].add);
    z[rt].add = 0;
}

int n, m;
int a[N];

void build(int l, int r, int rt) {
    if (l == r) {
        z[rt].init(a[l]);
        return;
    }
    int mid = (l + r) >> 1;
    build(lson);
    build(rson);
    z[rt] = z[rt << 1] + z[rt << 1 | 1];
}

node query(int l, int r, int rt, int nowl, int nowr) {
    if (nowl <= l && r <= nowr)
        return z[rt];
    pushcol(l, r, rt);
    int mid = (l + r) >> 1;
    if (nowl <= mid) {
        if (mid < nowr)
            return query(lson, nowl, nowr) + query(rson, nowl, nowr);
        else
            return query(lson, nowl, nowr);
    } else
        return query(rson, nowl, nowr);
}

void modify(int l, int r, int rt, int nowl, int nowr, int v) {
    if (nowl <= l && r <= nowr) {
        color(l, r, rt, v);
        return;
    }
    pushcol(l, r, rt);
    int mid = (l + r) >> 1;
    if (nowl <= mid)
        modify(lson, nowl, nowr, v);
    if (mid < nowr)
        modify(rson, nowl, nowr, v);
    z[rt] = z[rt << 1] + z[rt << 1 | 1];
}

2.4 Problem 4

2.4.1 题目简述

已知一个数列 \(\{a_i\}\),你需要进行下面两种操作:

  1. 将某区间每一个数加上 \(k\)
  2. 将某个区间每一个数都乘上 \(v\)

2.4.2 思路简述

多维护一个 \(mul\)

2.4.3 代码实现

#define int long long
#define root 1, n, 1
#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1 | 1

const int N = 1e5 + 5;

struct node {
    int sum;
    int siz;
    int mul;  //
    node() {
        sum = 0;
        siz = 0;
        mul = 1;  //
    }
    void init(int v) {
        sum = v;
        siz = 1;
    }
} z[N << 2];

node operator+(const node &l, const node &r) {
    node res;
    res.sum = l.sum + r.sum;
    res.siz = l.siz + r.siz;
    return res;
}

void color(int l, int r, int rt, int v) {  // 打标记
    z[rt].sum *= v;                        //
    z[rt].mul *= v;                        //
}

void pushcol(int l, int r, int rt) {  // 标记下放
    if (z[rt].mul == 1)               //           // 可有可无,有了会慢一点
        return;
    int mid = (l + r) >> 1;
    color(lson, z[rt].mul);  //
    color(rson, z[rt].mul);  //
    z[rt].mul = 0;           //
}

int n, m;
int a[N];

void build(int l, int r, int rt) {  // 建树
    if (l == r) {
        z[rt].init(a[l]);
        return;
    }
    int mid = (l + r) >> 1;
    build(lson);
    build(rson);
    z[rt] = z[rt << 1] + z[rt << 1 | 1];
}

node query(int l, int r, int rt, int nowl, int nowr) {  // 区间查询
    // nowl nowr 代表询问的区间
    // l r rt 描述了一个线段树节点
    if (nowl <= l && r <= nowr)
        return z[rt];
    pushcol(l, r, rt);
    int mid = (l + r) >> 1;
    if (nowl <= mid) {
        if (mid < nowr)
            return query(lson, nowl, nowr) + query(rson, nowl, nowr);
        else
            return query(lson, nowl, nowr);
    } else
        return query(rson, nowl, nowr);
}

void modify(int l, int r, int rt, int nowl, int nowr, int v) {
    if (nowl <= l && r <= nowr) {
        color(l, r, rt, v);  // 打懒标记
        return;
    }
    pushcol(l, r, rt);
    int mid = (l + r) >> 1;
    if (nowl <= mid)
        modify(lson, nowl, nowr, v);
    if (mid < nowr)
        modify(rson, nowl, nowr, v);
    z[rt] = z[rt << 1] + z[rt << 1 | 1];
}

2.5 Problem 5

2.5.1 题目简述

已知一个数列 \(\{a_i\}\),你需要进行下面两种操作:

  1. 将某区间每一个数加上 \(k\)
  2. 将某个区间加上一个等差数列。

2.5.2 思路简述

维护首项和公差。

2.5.3 代码实现

#define root 1,n,1
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1

const int maxn=100010;

int n,m,a[maxn];

struct node//一个线段树节点 
{
	int sum;//代表区间和
	int size;//代表区间长度 
	int x,y;//给这段区间加上了一个首项为x 公差为y的等差数列 
	node()
	{
		sum = size = x = y = 0;
	} 
	void init(int v)//用一个数初始化 
	{
		sum = v;
		size = 1;
	}
}z[maxn<<2];//z[i]就代表线段树的第i个节点 

node operator+(const node &l,const node &r)
{
	node res;
	
	res.sum = l.sum + r.sum;
	res.size = l.size + r.size;
	
	return res;
}

void color(int l,int r,int rt,int x, int y)//给l,r,rt这个节点加上一个首项为x公差为y的等差数列 
{
	z[rt].x += x;
	z[rt].y += y;
	z[rt].sum += (x+x+(size-1)*y) * size / 2;
} 

void push_col(int l,int r,int rt)//标记下放 把标记告诉儿子
{
	if (z[rt].x == 0 && z[rt].y == 0) return; //没标记 不需要下放  可以不要这句话 但会慢些 
	int m=(l+r)>>1;
	color(lson,z[rt].x,z[rt].y);
	color(rson,z[rt].x + z[rt<<1].size * y,z[rt].y);
	z[rt].x=z[rt].y=0;
} 

void build(int l,int r,int rt)//建树 初始化l,r,rt这个节点 
//编号为rt的线段树节点 所对应的区间是l~r 
{
	if (l==r)
	{
		z[rt].init(a[l]);
		return;
	} 
	int m=(l+r) >> 1;
	build(lson);
	build(rson);
	z[rt] = z[rt<<1] + z[rt<<1|1]; 
}

node query(int l,int r,int rt,int nowl,int nowr)
//l,r,rt描述了一个线段树节点
//nowl nowr代表了询问的区间的左端点和右端点 
{
	if (nowl <= l && r <= nowr) return z[rt];
	push_col(l,r,rt);
	int m=(l+r)>>1;
	if (nowl<=m)
	{
		if (m<nowr) return query(lson,nowl,nowr) + query(rson,nowl,nowr);
		else return query(lson,nowl,nowr);
	}
	else return query(rson,nowl,nowr);
}

void modify(int l,int r,int rt,int nowl,int nowr,int v)
//把nowl~nowr这段区间全部整体+v 
{
	if (nowl<=l && r<=nowr)//当前线段树节点被修改区间整体包含
	{
		color(l,r,rt,v);//给l,r,rt这个节点打一个+v的懒标记
		return; 
	} 
	push_col(l,r,rt);
	int m=(l+r)>>1;
	if (nowl<=m) modify(lson,nowl,nowr,v);
	if (m<nowr) modify(rson,nowl,nowr,v);
	z[rt] = z[rt<<1] + z[rt<<1|1];
}

3 P3373 【模板】线段树 2:线段树再拓展

3.1 题目简述

略。

3.2 思路简述

多维护 \(mul\)

3.3 代码实现

#define root 1,n,1
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1

const int maxn=100010;

int n,m,a[maxn];

struct node//一个线段树节点 
{
	int sum;//代表区间和
	int size;//代表区间长度 
	int add;
	int mul;
	//x*mul+add 
	node()
	{
		sum = size = add = 0;
		mul = 1; 
	} 
	void init(int v)//用一个数初始化 
	{
		sum = v;
		size = 1;
	}
}z[maxn<<2];//z[i]就代表线段树的第i个节点 

node operator+(const node &l,const node &r)
{
	node res;
	
	res.sum = l.sum + r.sum;
	res.size = l.size + r.size;
	
	return res;
}

void color(int l,int r,int rt,int mul,int add)//给l,r,rt这个节点打一个*mul+add的懒标记 
{
	res.mul *= mul;
	res.add = res.add * mul + add;
	res.sum = res.sum * mul + add * res.size;
} 

void push_col(int l,int r,int rt)//标记下放 把标记告诉儿子
{
	if (z[rt].mul == 1 && z[rt].add == 0) return; //没标记 不需要下放  可以不要这句话 但会慢些 
	int m=(l+r)>>1;
	color(lson,z[rt].mul,z[rt].add);
	color(rson,z[rt].mul,z[rt].add);
	z[rt].mul=1;z[rt].add=0;
} 

void build(int l,int r,int rt)//建树 初始化l,r,rt这个节点 
//编号为rt的线段树节点 所对应的区间是l~r 
{
	if (l==r)
	{
		z[rt].init(a[l]);
		return;
	} 
	int m=(l+r) >> 1;
	build(lson);
	build(rson);
	z[rt] = z[rt<<1] + z[rt<<1|1]; 
}

node query(int l,int r,int rt,int nowl,int nowr)
//l,r,rt描述了一个线段树节点
//nowl nowr代表了询问的区间的左端点和右端点 
{
	if (nowl <= l && r <= nowr) return z[rt];
	push_col(l,r,rt);
	int m=(l+r)>>1;
	if (nowl<=m)
	{
		if (m<nowr) return query(lson,nowl,nowr) + query(rson,nowl,nowr);
		else return query(lson,nowl,nowr);
	}
	else return query(rson,nowl,nowr);
}

void modify(int l,int r,int rt,int nowl,int nowr,int mul,int add)
//把nowl~nowr这段区间全部整体+v 
{
	if (nowl<=l && r<=nowr)//当前线段树节点被修改区间整体包含
	{
		color(l,r,rt,mul,add);//给l,r,rt这个节点打一个+v的懒标记
		return; 
	} 
	push_col(l,r,rt);
	int m=(l+r)>>1;
	if (nowl<=m) modify(lson,nowl,nowr,mul,add);
	if (m<nowr) modify(rson,nowl,nowr,mul,add);
	z[rt] = z[rt<<1] + z[rt<<1|1];
}

4 线段树 2.5:线段树再再拓展

4.1 题目简述

在 2 的基础上再加一个操作:

  • 将某区间每一个数都改为 \(v\)

其实这题是P1253 扶苏的问题

但是这是钟神3秒内在没有看原题的情况下自己想出来的题。

4.2 思路简述

暂略。

4.3 代码实现

#include<bits/stdc++.h>
#define ls x<<1
#define rs x<<1|1
#define int long long
using namespace std;
template<typename T>
void read(T &x){
	x=0;
	int f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-'){
			f=-f;
		}
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}
	x*=f;
	return;
}
int n,q;
const int MAXN=4e6+5;
int a[MAXN];
struct xd_tree{
	int l;
	int r;
	int tag1;
	int tag2;
	int maxn;
	bool flag;
};
xd_tree t[MAXN];
void pushup(int x){
	t[x].maxn=max(t[ls].maxn,t[rs].maxn);
	return;
}
void pushdown(int x){
	if(t[x].flag){
		t[ls].maxn=t[x].tag1+t[x].tag2;
		t[rs].maxn=t[x].tag1+t[x].tag2;
		t[ls].tag1=t[x].tag1;
		t[rs].tag1=t[x].tag1;
		t[ls].tag2=t[x].tag2;
		t[rs].tag2=t[x].tag2;
		t[ls].flag=1;
		t[rs].flag=1;
		t[x].flag=0;
		t[x].tag1=0;
		t[x].tag2=0;
		return;
	}
	else{
		t[ls].tag2+=t[x].tag2;
		t[rs].tag2+=t[x].tag2;
		t[ls].maxn+=t[x].tag2;
		t[rs].maxn+=t[x].tag2;		
		t[x].flag=0;
		t[x].tag1=0;
		t[x].tag2=0;
		return;
	}
}
void build(int x,int l,int r){
	t[x].l=l;
	t[x].r=r;
	t[x].maxn=-1e18;
	if(l==r){
		t[x].maxn=a[l];
		return;
	}
	else{
		int mid=l+r>>1;
		build(ls,l,mid);
		build(rs,mid+1,r);
		pushup(x);
	}
}
void Add(int x,int l,int r,int k){
	if(l<=t[x].l&&t[x].r<=r){
		t[x].tag1=k;
		t[x].tag2=0;
		t[x].maxn=k;
		t[x].flag=true;
		return;
	}
	pushdown(x);
	int mid=(t[x].l+t[x].r)/2;
	if(l<=mid){
		Add(ls,l,r,k);
	} 
	if(mid+1<=r){
		Add(rs,l,r,k);
	}
    pushup(x);
}
void pingtui(int x,int l,int r,int k){
	if(l<=t[x].l&&t[x].r<=r){
		t[x].tag2+=k;
		t[x].maxn+=k;
        return;
    }
    pushdown(x);
    int mid=(t[x].l+t[x].r)/2;
    if(l<=mid){
    	pingtui(ls,l,r,k); 
	}
	if(mid+1<=r){
		pingtui(rs,l,r,k);
	}
    pushup(x);
}
int query(int x,int l,int r){
	if(l<=t[x].l&&t[x].r<=r){
		return t[x].maxn;
	}
	pushdown(x);
	int mid=(t[x].l+t[x].r)/2;
	int ans=-1e18;
	if(l<=mid){
		ans=max(ans,query(ls,l,r));
	}
	if(mid+1<=r){
		ans=max(ans,query(rs,l,r));
	}
    return ans;
}
signed main(){
	read(n);
	read(q);
	for(int i=1;i<=n;i++){
		read(a[i]);
	}
	build(1,1,n);
	for(int i=1;i<=q;i++){
		int opt;
		read(opt);
		if(opt==1){
			int l,r,x;
			read(l);
			read(r);
			read(x);
			Add(1,l,r,x);
		} 
		else if(opt==2){
			int l,r,x;
			read(l);
			read(r);
			read(x);
			pingtui(1,l,r,x);
		}
		else{
			int l,r;
			read(l);
			read(r);
			cout<<query(1,l,r)<<'\n';
		}
	}
	return 0;
}

5 GSS系列题目:线段树应用

闲话:1345可做,678不建议做。

5.1 题目简述

在普通线段树基础上将询问操作改为求最大子段和。

5.2 思路简述

如题,维护最大子段和。

5.3 代码实现

CSS1代码如下。这份是我把P4513 小白逛公园的代码改了一下交的,单点修改的代码我没删。

#include <bits/stdc++.h>
using namespace std;

const int N = 5e5 + 5;

#define int long long
#define root 1, n, 1
#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1 | 1

int n, m;
int a[N];

struct node {
    int sum;
    int fro;
    int bak;
    int siz;
    int ans;
    node() {
        sum = 0;
        fro = 0;
        bak = 0;
        ans = -1e18;
    }
    void init(int x) {
        sum = x;
        fro = x;
        bak = x;
        ans = x;
    }
} z[N << 2];

node operator+(const node &l, const node &r) {
    node res;
    res.sum = l.sum + r.sum;
    res.siz = l.siz + r.siz;
    res.fro = max(l.fro, l.sum + r.fro);
    res.bak = max(r.bak, r.sum + l.bak);
    res.ans = max(max(l.ans, r.ans), l.bak + r.fro);
    return res;
}

void build(int l, int r, int rt) {  // 建树
    if (l == r) {
        z[rt].init(a[l]);
        return;
    }
    int mid = (l + r) >> 1;
    build(lson);
    build(rson);
    z[rt] = z[rt << 1] + z[rt << 1 | 1];
}

node query(int l, int r, int rt, int nowl, int nowr) {  // 区间查询
    // nowl nowr 代表询问的区间
    // l r rt 描述了一个线段树节点
    if (nowl <= l && r <= nowr)
        return z[rt];
    int mid = (l + r) >> 1;
    if (nowl <= mid) {
        if (mid < nowr)
            return query(lson, nowl, nowr) + query(rson, nowl, nowr);
        else
            return query(lson, nowl, nowr);
    } else
        return query(rson, nowl, nowr);
}

void update(int l, int r, int x, int p, int w) {  // 以x为根的子树、l到r的区间中把p结点修改为w
    if (l == r) {                                 // 找到该结点,修改
        z[x].sum = w;
        z[x].fro = w;
        z[x].bak = w;
        z[x].ans = w;
        return;
    }
    int mid = (l + r) >> 1;
    if (p <= mid)  // 与查询一样
        update(l, mid, x << 1, p, w);
    else
        update(mid + 1, r, x << 1 | 1, p, w);
    z[x] = z[x << 1] + z[x << 1 | 1];
}
signed main() {
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    build(root);
    cin >> m;
    for (int i = 1; i <= m; i++) {
        int l, r;
        cin >> l >> r;
        if (l > r)
            swap(l, r);
        cout << query(root, l, r).ans << "\n";
    }

    return 0;
}

6 可持久化线段树

没听懂,心态炸。

https://www.cnblogs.com/RabbitHu/p/segtree.html

6.1 题目简述

正在研究。

6.2 思路简述

正在研究。

6.3 代码实现

这是钟神的。

#include<bits/stdc++.h>

using namespace std;
int cnt;//总共有多少个节点 
struct node
{
	int l,r;//左儿子 右儿子编号 
	int sum;//区间和 
	node(){
		l=r=sum=0;
	}
}z[maxn*logn];

void update(int p)
{
	z[p].sum = z[z[p].l].sum + z[z[p].r].sum;
}

int build(int l,int r)//当前的区间为l~r 是这段区间对应的节点编号 
{
	cnt++;
	int p=cnt;
	if (l==r)
	{
		z[p].sum = a[l];
		return p;
	}
	int m=(l+r)>>1;
	z[p].l = build(l,m);
	z[p].r = build(m+1,r);
	update(p);
	return p;
}

int query(int l,int r,int rt,int nowl,int nowr)
//当前线段树节点编号为rt 对应的区间为l~r 要询问nowl~nowr这段区间的和
{
	if (nowl <= l && r <= nowr) return z[rt].sum;
	int m=(l+r)>>1;
	if (nowl<=m)
	{
		if (m<nowr) return query(l,m,z[rt].l,nowl,nowr) + query(m+1,r,z[rt].r,nowl,nowr);
		else return query(l,m,z[rt].l,nowl,nowr);
	}
	else return query(m+1,r,z[rt].r,nowl,nowr);
} 

int modify(int l,int r,int rt,int p,int v)//返回修改后的新节点编号 
//当前线段树节点编号为rt 对应的区间为l~r 要把a[p]+=v 
{
	cnt++;int q = cnt;//新的节点q用于修改
	z[q] = z[rt]; 
	if (l==r)
	{
		z[q].sum += v;
		return q;
	}
	int m=(l+r)>>1;
	if (p<=m)//在左儿子
		z[q].l = modify(l,m,z[q].l,p,v);
	else 
		z[q].r = modify(m+1,r,z[q].r,p,v);
	update(q);
	return q;
}

int main()
{
	cin >> n;
	for (int i=1;i<=n;i++)
		cin >> a[i];
	cin >> m;
	root[0] = build(1,n);//root[i]代表第i次操作后的根节点是谁
	for (int i=1;i<=m;i++) 
	{
		int opt;
		cin >> opt;
		if (opt==1)
		{
			int p,v;
			cin >> p >> v;
			root[i] = modify(1,n,root[i-1],p,v);
		}
		else
		{
			int k,l,r;
			cin >> k >> l >> r;
			cout << query(1,n,root[k],l,r) << "\n";
			root[i] = root[i-1];
		}
	}
	
	return 0;
}

7 主席树

没听懂,心态更炸。

6.1 题目简述

正在研究。

6.2 思路简述

正在研究。

6.3 代码实现

这是钟神的。

#include<bits/stdc++.h>

using namespace std;
int cnt;//总共有多少个节点 
struct node
{
	int l,r;//左儿子 右儿子编号 
	int sum;//区间和 
	node(){
		l=r=sum=0;
	}
}z[maxn*logn];

void update(int p)
{
	z[p].sum = z[z[p].l].sum + z[z[p].r].sum;
}

int query(int l,int r,int rt,int nowl,int nowr)
//当前线段树节点编号为rt 对应的区间为l~r 要询问nowl~nowr这段区间的和
{
	if (nowl <= l && r <= nowr) return z[rt].sum;
	int m=(l+r)>>1;
	if (nowl<=m)
	{
		if (m<nowr) return query(l,m,z[rt].l,nowl,nowr) + query(m+1,r,z[rt].r,nowl,nowr);
		else return query(l,m,z[rt].l,nowl,nowr);
	}
	else return query(m+1,r,z[rt].r,nowl,nowr);
} 

int modify(int l,int r,int rt,int p,int v)//返回修改后的新节点编号 
//当前线段树节点编号为rt 对应的区间为l~r 要把a[p]+=v 
{
	cnt++;int q = cnt;//新的节点q用于修改
	z[q] = z[rt]; 
	if (l==r)
	{
		z[q].sum += v;
		return q;
	}
	int m=(l+r)>>1;
	if (p<=m)//在左儿子
		z[q].l = modify(l,m,z[q].l,p,v);
	else 
		z[q].r = modify(m+1,r,z[q].r,p,v);
	update(q);
	return q;
}

int query(int p1,int p2,int l,int r,int k)
//当前对应的值域范围为l~r
//要询问第k小的数
//需要用p1和p2这两颗线段树来询问 
{
	if (l==r) return l;
	int m=(l+r)>>1;
	if (z[z[p2].l].sum - z[z[p1].l].sum >= k) return query(z[p1].l,z[p2].l,l,m,k);
	//z[z[p2].l].sum - z[z[p1].l].sum代表aL~aR有多少个数在[l,m]之间 
	else return query(z[p1].r,z[p2].r,m+1,r,k-(z[z[p2].l].sum - z[z[p1].l].sum));
}

int main()
{
	cin >> n;
	for (int i=1;i<=n;i++)
		cin >> a[i];
	root[0] = 0;
	//root[i] 代表a1~ai这些数所对应的值域线段树的根 
	//值域范围是1~maxv 
	for (int i=1;i<=n;i++)
		root[i] = modify(1,maxv,root[i-1],a[i],1);
	cin >> m;
	for (int i=1;i<=m;i++)
	{
		int l,r,k;
		cin >> l >> r >> k;
		cout << query(root[l-1],root[r],1,maxv,k) << "\n";
	}
	
	return 0;
}

8 取模相关

取模的速度相当慢,所以在保证正确性的前提下尽量少用。

#include<bits/stdc++.h>
using namespace std;

const int mo = 1000000007;

inline void inc(int &a,int b){
	a+=b;if (a>=mo) a-=mo;
}
//#define inc(a,b) {a+=b;if (a>=mo) a-=mo;}

int main(){
    //a = b*c+d*e
    a = (1ll * b * c + 1ll * d * e) % mo;
	
	//a = b+c*d;
	a = (b + 1ll * c * d) % mo;
	
	//a = b*c*d + e*f + g;
	a = (1ll * b * c % mo * d + 1ll * e * f + g) % mo;
	
	//a = a + b;
	a += b; if (a >= mo) a-=mo;
	
	//a = b*c-d*e
	a = ((1ll * b * c - 1ll * d * e) % mo + mo) % mo;
	
	//a = a - b
	a -= b; if (a<0) a += mo;
	
	return 0;
}
posted @ 2025-05-02 19:25  chaqjs  阅读(34)  评论(0)    收藏  举报