数据结构:线段树

1,模板

这里推荐一篇博客,原理讲的清晰易懂,配合着讲解更容易理解模板的思想

(http://t.csdn.cn/AaXFB)

(1)建树

void build(int p, int l, int r)//创建线段树,id表示存储下标,区间[L,r]
{
    tr[p].l = l, tr[p].r = r, tr[p].lz = 0;
    if (l == r)//左端点等于右端点,即为叶子节点(区间长度为1),直接赋值即可
    {
        tr[p].sum = a[l];
        return;
    }
    // 否则将当前区间中间拆开成两个区间
    int mid = (l + r) / 2;//mid则为中间点,左儿子的结点区间为[l,mid],右儿子的结点区间为[mid + 1,r]
    build(p * 2, l, mid); //递归构造左儿子结点
    build(p * 2 + 1, mid + 1, r); //递归构造右儿子结点
    //tr[p].sum = min(tr[p * 2].sum, tr[p * 2 + 1].sum);//求区间最小值
    tr[p].sum = tr[p * 2].sum + tr[2 * p + 1].sum;//求区间之和
    return;
}

(2)单点修改

void add(int i, int dis, int k) {
    if (tr[i].l == tr[i].r) {// 找到长度为 1 的区间才返回
        tr[i].sum += k;
        return;
    }
    if (dis <= tr[i * 2].r)  add(i * 2, dis, k);
    else  add(i * 2 + 1, dis, k);

    tr[i].sum = tr[i * 2].sum + tr[i * 2 + 1].sum;//更新区间和
    return;
}

(3)单点查询

int find(int index, int l, int r) {//index是要查找的元素下标
    if (l==r) {
        return tr[l].sum;
    }
    int mid = (l + r) / 2;
    if (index <= mid) {
        return find(index, l, mid);
    }
    else {
        return find(index, mid + 1, r);
    }
}

(4)区间修改

void push_down(int p)
{
    if (tr[p].lz)//如果id有lazy标记
    {
        tr[p * 2].lz += tr[p].lz;//将它的左孩子的lazy加上它的lazy
        tr[p * 2 + 1].lz += tr[p].lz;//将它的右孩子的lazy加上它的lazy
        int mid = (tr[p].l + tr[p].r) / 2;
        tr[p * 2].sum += tr[p].lz * (mid - tr[2 * p].l + 1);//左孩子的Q+它下放的Q*区间长度
        tr[p * 2 + 1].sum += tr[p].lz * (tr[2 * p + 1].r - mid);
        tr[p].lz = 0;//清空lazy标记
    }
}

void query(int p, int l, int r, int k)
{
    if (tr[p].l >= l && tr[p].r <= r)//被[l,r]包含了
    {
        tr[p].lz += k;//暂时不下放,加进lazy标记中
        tr[p].sum += (tr[p].r - tr[p].l + 1) * k;
        return;
    }
    push_down(p);//要来更新下面节点了,赶紧下放
    int mid = (tr[p].l + tr[p].r) / 2;
    if (l <= mid) query(p * 2, l, r, k);//因为只有x<=mid(即[l,mid]有一部分是被[x,y]覆盖了的)才需要去更新[l,mid]
    if (r > mid) query(p * 2 + 1, l, r, k);
    tr[p].sum = tr[2 * p].sum + tr[2 * p + 1].sum;//子节点更新完之后父节点当然也要更新(上升操作)
}

(5)区间替换

void push_down(int p) {
    // 该函数将父节点的懒惰更新(lz)传递给其子节点。
    if (tr[p].lz) {
        tr[2 * p].sum = tr[p].lz * (tr[2 * p].r - tr[2 * p].l + 1);
        tr[2 * p + 1].sum = tr[p].lz * (tr[2 * p + 1].r - tr[2 * p + 1].l + 1);
        tr[2 * p].lz = tr[p].lz;
        tr[2 * p + 1].lz = tr[p].lz;
        // 重置当前节点的懒惰更新值。
        tr[p].lz = 0;
    }
}

void update(int p, int l, int r, int k) {
    // 如果当前节点的范围完全包含在查询范围内,执行懒惰更新并返回。
    if (l <= tr[p].l && tr[p].r <= r) {
        tr[p].sum = (tr[p].r - tr[p].l + 1) * k;
        tr[p].lz = k;
        return;
    }

    push_down(p);//下放

    int m = (tr[p].l + tr[p].r) >> 1;

    if (l <= m) update(2 * p, l, r, k);
    if (r > m) update(2 * p + 1, l, r, k);

    // 基于更新后的子节点和更新当前节点的和。
    tr[p].sum = tr[2 * p].sum + tr[2 * p + 1].sum;
}

(6)区间求和

int find(int p, int l, int r)
{
    if (tr[p].l >= l && tr[p].r <= r) return tr[p].sum;//[l,r]被[x,y]包含了
    push_down(p);//要查到id的子节点了,赶紧下放
    int ans = 0;
    if (tr[2*p].r >= l) ans += find(p * 2, l, r);//ans+=左孩子和
    if (tr[2*p+1].l <= r) ans += find(p * 2 + 1, l, r);//ans+=右孩子和
    return ans;
}

2,模板题练习

1,区间修改,区间求和   【模板】树状数组 1 - 洛谷

#include
using namespace std;
#define int long long//防止溢出
const int N = 1e5 + 5;
int a[N];

struct node
{
    int l, r, sum;
    int lz;
}tr[N * 4];//4倍空间

void build(int p, int l, int r);
void push_down(int p);
void query(int p, int l, int r, int k);
int find(int, int , int );

signed main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    build(1, 1, n);
    for (int i = 0; i < m; i++) {
        int flag, l, r, k;
        cin >> flag;
        if (flag == 1) {
            cin >> l >> r >> k;
            query(1, l, r, k);
        }
        else {
            cin >> l >> r;
            cout << find(1, l, r)<= l && tr[p].r <= r)//被[l,r]包含了
    {
        tr[p].lz += k;//暂时不下放,加进lazy标记中
        tr[p].sum += (tr[p].r - tr[p].l + 1) * k;
        return;
    }
    push_down(p);//要来更新下面节点了,赶紧下放
    int mid = (tr[p].l + tr[p].r) / 2;
    if (l <= mid) query(p * 2, l, r, k);//因为只有x<=mid(即[l,mid]有一部分是被[x,y]覆盖了的)才需要去更新[l,mid]
    if (r > mid) query(p * 2 + 1, l, r, k);
    tr[p].sum = tr[2 * p].sum + tr[2 * p + 1].sum;//子节点更新完之后父节点当然也要更新(上升操作)
}

int find(int p, int l, int r)
{
    if (tr[p].l >= l && tr[p].r <= r) return tr[p].sum;//[l,r]被[x,y]包含了
    push_down(p);//要查到id的子节点了,赶紧下放
    int ans = 0;
    if (tr[2*p].r >= l) ans += find(p * 2, l, r);//ans+=左孩子和
    if (tr[2*p+1].l <= r) ans += find(p * 2 + 1, l, r);//ans+=右孩子和
    return ans;
}

2,单点修改,区间查询   【模板】树状数组 1 - 洛谷

#include
using namespace std;
#define int long long//防止溢出
const int N = 5e5 + 5;
int a[N];

struct node
{
    int l, r, sum;
    int lz;
}tr[N * 4];//4倍空间

void build(int p, int l, int r);
void add(int i, int dis, int k);
int find(int p, int l, int r);

signed main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    build(1, 1, n);
    for (int i = 0; i < m; i++) {
        int flag, l, r, pos, k;
        cin >> flag;
        if (flag == 1) {
            cin >> pos >> k;
            add(1, pos, k);
        }
        else {
            cin >> l >> r;
            cout << find(1, l, r)<= l && tr[p].r <= r) return tr[p].sum;//[l,r]被[x,y]包含了
    //push_down(p);//要查到id的子节点了,赶紧下放
    int ans = 0;
    if (tr[2 * p].r >= l) ans += find(p * 2, l, r);//ans+=左孩子和
    if (tr[2 * p + 1].l <= r) ans += find(p * 2 + 1, l, r);//ans+=右孩子和
    return ans;
}

3,区间修改,单点查询  【模板】树状数组 2 - 洛谷

Tip:这题输入和输出最好用scanf()和printf(),不然可能会超时.

#include
using namespace std;
#define int long long//防止溢出
const int N = 5e5 + 5;
int a[N];

struct node
{
    int l, r, sum;
    int lz;
}tr[N * 4];//4倍空间

void build(int p, int l, int r);
void push_down(int p);
void query(int p, int l, int r, int k);
int find(int index, int l, int r);

signed main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    build(1, 1, n);
    for (int i = 0; i < m; i++) {
        int flag, l, r, pos, k;
        cin >> flag;
        if (flag == 1) {
            cin >> l >> r >> k;
            query(1, l, r, k);
        }
        else {
            cin >> l;
            cout << find(1, l, l)<= l && tr[p].r <= r)//被[l,r]包含了
    {
        tr[p].lz += k;//暂时不下放,加进lazy标记中
        tr[p].sum += (tr[p].r - tr[p].l + 1) * k;
        return;
    }
    push_down(p);//要来更新下面节点了,赶紧下放
    int mid = (tr[p].l + tr[p].r) / 2;
    if (l <= mid) query(p * 2, l, r, k);//因为只有x<=mid(即[l,mid]有一部分是被[x,y]覆盖了的)才需要去更新[l,mid]
    if (r > mid) query(p * 2 + 1, l, r, k);
    tr[p].sum = tr[2 * p].sum + tr[2 * p + 1].sum;//子节点更新完之后父节点当然也要更新(上升操作)
}

int find(int p, int l, int r)
{
    if (tr[p].l >= l && tr[p].r <= r) return tr[p].sum;//[l,r]被[x,y]包含了
    push_down(p);//要查到id的子节点了,赶紧下放
    int ans = 0;
    if (tr[2 * p].r >= l) ans += find(p * 2, l, r);//ans+=左孩子和
    if (tr[2 * p + 1].l <= r) ans += find(p * 2 + 1, l, r);//ans+=右孩子和
    return ans;
}

4,区间乘法  【模板】线段树 2 - 洛谷

Tip:要写两个懒人标记分别表示加法和乘法,这里的输入和输出也是要用scanf()和printf().超时警告!!!

#include 
using namespace std;

const int N = 1e5 + 5;
int a[N], mod;

struct node {
    int l, r, sum;  // 左右区间边界和当前区间元素和
    int ad, mul;    // 懒标记:加法和乘法
} tr[N * 4];

// 函数声明
void build(int p, int l, int r);
void push_down(int p);
void add(int p, int l, int r, int k);
void mult(int p, int l, int r, int k);
int search(int p, int l, int r);

signed main() {
    int n, m;
    cin >> n >> m >> mod; // 输入数列个数n,操作个数m,模数mod
    for (int i = 1; i <= n; i++) cin >> a[i]; // 输入数列元素值
    build(1, 1, n); // 构建线段树
    for (int i = 0; i < m; i++) {
        int flag, l, r, k;
        cin >> flag; // 输入操作标识
        if (flag == 1) {
            cin >> l >> r >> k; // 输入区间 [l, r] 和乘法因子 k
            mult(1, l, r, k); // 执行乘法操作
        }
        else if (flag == 2) {
            cin >> l >> r >> k; // 输入区间 [l, r] 和加法值 k
            add(1, l, r, k); // 执行加法操作
        }
        else {
            cin >> l >> r; // 输入区间 [l, r]
            cout << search(1, l, r) << endl; // 输出区间元素和对模数取模的结果
        }
    }
    return 0;
}

void build(int p, int l, int r) {
    tr[p].l = l, tr[p].r = r, tr[p].mul = 1;
    if (l == r) {
        tr[p].sum = a[l] % mod; // 叶节点直接记录数列元素对模数取模的值
        return;
    }
    int mid = (l + r) / 2;
    build(2 * p, l, mid); // 递归构建左子树
    build(2 * p + 1, mid + 1, r); // 递归构建右子树
    tr[p].sum = (tr[2 * p].sum + tr[2 * p + 1].sum) % mod; // 计算当前节点的元素和
}

void push_down(int p) {
    // 将当前节点的懒标记向下传递给子节点
    tr[2 * p].sum = (tr[2 * p].sum * tr[p].mul + tr[p].ad * (tr[2 * p].r - tr[2 * p].l + 1)) % mod;
    tr[2 * p + 1].sum = (tr[2 * p + 1].sum * tr[p].mul + tr[p].ad * (tr[2 * p + 1].r - tr[2 * p + 1].l + 1)) % mod;

    tr[2 * p].mul = (tr[2 * p].mul * tr[p].mul) % mod;
    tr[2 * p + 1].mul = (tr[2 * p + 1].mul * tr[p].mul) % mod;

    tr[2 * p].ad = (tr[2 * p].ad * tr[p].mul + tr[p].ad) % mod;
    tr[2 * p + 1].ad = (tr[2 * p + 1].ad * tr[p].mul + tr[p].ad) % mod;

    tr[p].ad = 0; // 清空当前节点的懒标记
    tr[p].mul = 1;
    return;
}

void add(int p, int l, int r, int k) {
    if (tr[p].l >= l && tr[p].r <= r) {
        tr[p].ad = (tr[p].ad + k) % mod;
        tr[p].sum = (tr[p].sum + (tr[p].r - tr[p].l + 1) * k) % mod;
        return;
    }
    push_down(p);
    int mid = (tr[p].l + tr[p].r) / 2;
    if (l <= mid) add(2 * p, l, r, k);
    if (r > mid) add(2 * p + 1, l, r, k);
    tr[p].sum = (tr[2 * p].sum + tr[2 * p + 1].sum) % mod;
    return;
}

void mult(int p, int l, int r, int k) {
    if (tr[p].l >= l && tr[p].r <= r) {
        tr[p].ad = (tr[p].ad * k) % mod;
        tr[p].mul = (tr[p].mul * k) % mod;
        tr[p].sum = (tr[p].sum * k) % mod;
        return;
    }
    push_down(p);
    int mid = (tr[p].l + tr[p].r) / 2;
    if (l <= mid) mult(2 * p, l, r, k);
    if (r > mid) mult(2 * p + 1, l, r, k);
    tr[p].sum = (tr[2 * p].sum + tr[2 * p + 1].sum) % mod;
}

int search(int p, int l, int r) {
    if (tr[p].l >= l && tr[p].r <= r) return tr[p].sum;  //[l,r]被[x,y]包含了
    push_down(p);  // 要查到id的子节点了,赶紧下放
    int ans = 0;
    if (tr[2 * p].r >= l) ans = (ans + search(p * 2, l, r)) % mod;  // ans+=左孩子和
    if (tr[2 * p + 1].l <= r) ans = (ans + search(p * 2 + 1, l, r)) % mod;  // ans+=右孩子和
    return ans;
}

5,区间替换,区间求和  线段树2 - Virtual Judge

Tip:同样的,这里的输入和输出也是要用scanf()和printf().

#include
using namespace std;
const int N = 1e5 + 5;
int a[N];

struct node
{
	int l, r, sum;
	int lz;
}tr[N*4];

void build(int p, int l, int r);
void push_down(int p);
void update(int p, int l, int r, int k);

int main()
{
	int T, count = 0;
	cin >> T;
	while (T--) {
		int n, m;
		cin >> n >> m;
		for (int i = 1; i <= n; i++) a[i] = 1;
		build(1, 1, n);
		for (int i = 0; i < m; i++) {
			int l, r, k;
			cin >> l >> r >> k;
			update(1, l, r, k);
		}
		count++;
		cout << "Case " << count << ": The total value of the hook is " << tr[1].sum << "." << endl;
	}
	return 0;
}

void build(int p, int l, int r)//创建线段树,id表示存储下标,区间[L,r]
{
	tr[p].l = l, tr[p].r = r, tr[p].lz = 0;
	if (l == r)//左端点等于右端点,即为叶子节点(区间长度为1),直接赋值即可
	{
		tr[p].sum = a[l];
		return;
	}
	// 否则将当前区间中间拆开成两个区间
	int mid = (l + r) / 2;//mid则为中间点,左儿子的结点区间为[l,mid],右儿子的结点区间为[mid + 1,r]
	build(p * 2, l, mid); //递归构造左儿子结点
	build(p * 2 + 1, mid + 1, r); //递归构造右儿子结点
	//tr[p].sum = min(tr[p * 2].sum, tr[p * 2 + 1].sum);//求区间最小值
	tr[p].sum = tr[p * 2].sum + tr[2 * p + 1].sum;//求区间之和
	return;
}

void push_down(int p) {
	// 该函数将父节点的懒惰更新(lz)传递给其子节点。
	if (tr[p].lz) {
		tr[2 * p].sum = tr[p].lz * (tr[2 * p].r - tr[2 * p].l + 1);
		tr[2 * p + 1].sum = tr[p].lz * (tr[2 * p + 1].r - tr[2 * p + 1].l + 1);
		tr[2 * p].lz = tr[p].lz;
		tr[2 * p + 1].lz = tr[p].lz;
		// 重置当前节点的懒惰更新值。
		tr[p].lz = 0;
	}
}

void update(int p, int l, int r, int k) {
	// 如果当前节点的范围完全包含在查询范围内,执行懒惰更新并返回。
	if (l <= tr[p].l && tr[p].r <= r) {
		tr[p].sum = (tr[p].r - tr[p].l + 1) * k;
		tr[p].lz = k;
		return;
	}

	push_down(p);//下放

	int m = (tr[p].l + tr[p].r) >> 1;

	if (l <= m) update(2 * p, l, r, k);
	if (r > m) update(2 * p + 1, l, r, k);

	// 基于更新后的子节点和更新当前节点的和。
	tr[p].sum = tr[2 * p].sum + tr[2 * p + 1].sum;
}

6,单点替换,区间极值   线段树2 - Virtual Judge

#define _CRT_SECURE_NO_WARNINGS
#include
using namespace std;
const int N = 1e5 + 5;
int a[N];

struct node
{
    int l, r, sum;
    int lz;
}tr[N * 4];//4倍空间

void build(int p, int l, int r);
void update(int p, int pos, int k);
int query(int p, int l, int r);

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);//提高输入输出效率
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++) cin >> a[i];
	build(1, 1, n);
	for (int i = 0; i < m; i++) {
		char flag;
		int l, r;
		cin >> flag >> l >> r;
		if (flag == 'U') update(1, l, r);
		else cout << query(1, l, r) << endl;
	}
	return 0;
}

void build(int p, int l, int r)//创建线段树,id表示存储下标,区间[L,r]
{
    tr[p].l = l, tr[p].r = r, tr[p].lz = 0;
    if (l == r)//左端点等于右端点,即为叶子节点(区间长度为1),直接赋值即可
    {
        tr[p].sum = a[l];
        return;
    }
    // 否则将当前区间中间拆开成两个区间
    int mid = (l + r) / 2;//mid则为中间点,左儿子的结点区间为[l,mid],右儿子的结点区间为[mid + 1,r]
    build(p * 2, l, mid); //递归构造左儿子结点
    build(p * 2 + 1, mid + 1, r); //递归构造右儿子结点
    tr[p].sum = max(tr[p * 2].sum, tr[p * 2 + 1].sum);
    return;
}

void update(int i, int dis, int k) {
    if (tr[i].l == tr[i].r) {// 找到长度为 1 的区间才返回
        tr[i].sum = k;
        return;
    }
    if (dis <= tr[i * 2].r)  update(i * 2, dis, k);
    else  update(i * 2 + 1, dis, k);

    tr[i].sum = max(tr[i * 2].sum, tr[i * 2 + 1].sum);//更新区间和
    return;
}

int query(int p, int l, int r)
{
    if (tr[p].l >= l && tr[p].r <= r) return tr[p].sum;//[l,r]被[x,y]包含了
    //push_down(p);//要查到id的子节点了,赶紧下放
    int ans = 0;
    if (tr[2 * p].r >= l) ans = max(ans, query(p * 2, l, r));//ans+=左孩子和
    if (tr[2 * p + 1].l <= r) ans = max(ans, query(p * 2 + 1, l, r));//ans+=右孩子和
    return ans;
}

你可能感兴趣的:(算法)