|算法讨论|线段树1(大白书版本) 学习笔记

常用方法:

1、点修改,区间查询

2、区间增加,区间查询

3、区间修改,区间查询

4、混合多种修改,区间查询

5、离散化操作

6、二维线段树


1、点修改,区间查询

input:
10 6
5 4 8 9 7 2 4 1 5 7
1 1 10
0 8 6
1 1 10
1 2 5
0 4 -8
1 1 5
//n m
//n个数,表示线段树节点的初始值
//m行,每行一个指令
//0 p v,修改p节点为v
//1 x y,求x~y区间最小值

output:
1
2
4
-8
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<set>
#include<string>
#define ms(i,j) memset(i,j,sizeof i);
using namespace std;
const int MAXN = 1000 + 5;
int n,m;
int mino[MAXN];//最小值 
int p,v; 
int update(int o, int l, int r)//更新a[p] = v; 
{
	int m = (l+r)/2;
	int lc = o*2, rc = o*2+1;
	if (l==r) mino[o] = v;
	else
	{
		if (p<=m) update(lc, l, m); else update(rc, m+1, r);
		mino[o] = min(mino[lc], mino[rc]);
	}
}
int x,y;
int query(int o, int l ,int r)//查询x,y区间最小值 
{
	int m = (l+r)/2;
	int lc = o*2, rc = o*2+1;
	int ans = 100000000;
	if (x<=l&&r<=y) return mino[o];
	if (x<=m) ans = min(ans, query(lc, l, m));
	if (m<y) ans = min(ans, query(rc, m+1, r));
	return ans;
}
int main()
{
    freopen("st.in", "r", stdin);freopen("st.out", "w", stdout);
    scanf("%d%d", &n ,&m);
    for (int i=1;i<=n;i++)//输入初值
    {
        scanf("%d", &v);
        p = i;
        update(1,1,n);//利用修改值来赋值初值 
    }
    for (int i=1;i<=m;i++)//输入m个指令 
    {
        int type;
        scanf("%d", &type);
        if (type==1)//求x,y的最小值 
        {
            scanf("%d%d", &x, &y);
            printf("%d\n", query(1,1,n));
        } else//修改a[p] = v; 
        {
            scanf("%d%d", &p, &v);
            update(1,1,n);
        }
    }
    fclose(stdin);fclose(stdout);
    return 0;
}


2、区间增加,区间查询

input:
10 10
5 4 8 9 7 2 4 1 5 7
1 1 10
2 1 10
3 1 10
0 6 7 10
2 2 8
3 2 5
0 4 6 -7
3 4 8
0 10 10 -17
2 7 10
//n m
//n个数,表示线段树节点的初始值
//m行,每行一个指令
//0 x y v,把x~y增加v
//1 x y,求x~y区间和
//2 x y,求x~y区间最小值
//3 x y,求x~y区间最大值
output:
52
1
9
1
9
14
-10
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ms(i,j) memset(i,j,sizeof i);
using namespace std;
const int MAXN = 1000 + 5;
int n,m;
int _max, _min, _sum;
int maxv[MAXN], minv[MAXN], addv[MAXN], sumv[MAXN]; 
int mt(int o, int l, int r)//更新结点信息 
{
	int m = (l+r)/2;
	int lc = o*2, rc = o*2+1;
	if (r>l)
	{
		sumv[o] = sumv[lc]+sumv[rc];
		minv[o] = min(minv[lc], minv[rc]);
		maxv[o] = max(maxv[lc], maxv[rc]);
	} 
	minv[o] += addv[o]; maxv[o] += addv[o]; sumv[o] += (r-l+1)*addv[o];
}
int x,y,v;
int update(int o, int l, int r)//使x,y区间都加上v 
{
	int m = (l+r)/2;
	int lc = o*2, rc = o*2+1;
	if (x<=l&&r<=y)
	{
		addv[o] = v;
	} else
	{
		if (x<=m) update(lc, l, m);
		if (m<y) update(rc, m+1, r);
	}
	mt(o, l ,r);
}
int query(int o, int l, int r, int add)//查询x,区间信息 
{
	int m = (l+r)/2;
	int lc = o*2, rc = o*2+1;
	if (x<=l&&r<=y)
	{
		_sum += sumv[o] + add * (r-l+1);
		_min = min(_min, minv[o]+add);
		_max = max(_max, maxv[o]+add);
	}else
	{
		if (x<=m) query(lc, l, m,add+addv[o]);
		if (m<y) query(rc, m+1, r,add+addv[o]);
	}
}
int main()
{
    freopen("st.in", "r", stdin);freopen("st.out", "w", stdout);
    scanf("%d%d", &n, &m);
     for (int i=1;i<=n;i++)//输入初值 
    {
        scanf("%d", &v);
        x = y = i;
        update(1,1,n);
    }
    for (int i=1;i<=m;i++)//输入指令 
    {
        int type;
        scanf("%d", &type);
        if (type==0)
        {
           scanf("%d%d%d", &x, &y, &v);
           update(1,1,n);
        } else
        {
           scanf("%d%d", &x, &y);
           _sum=_max=0; _min=100000000;
           query(1,1,n,0);
           switch(type)
           {
              case 1: printf("%d\n", _sum); break;
              case 2: printf("%d\n", _min); break;
              case 3: printf("%d\n", _max); break;
           }
        }
    }
    fclose(stdin);fclose(stdout);
    return 0;
}


3、区间修改,区间查询

input:
10 10
5 4 8 9 7 2 4 1 5 7
1 1 10
2 1 10
3 1 10
0 6 7 10
2 2 8
3 2 5
0 4 6 2
3 4 8
0 10 10 17
2 7 10
//n m
//n个数,表示线段树节点的初始值
//m行,每行一个指令
//0 x y v,把x~y修改为v(v>=0)
//1 x y,求x~y区间和
//2 x y,求x~y区间最小值
//3 x y,求x~y区间最大值
output:
52
1
9
1
9
10
1
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ms(i,j) memset(i,j,sizeof i);
using namespace std;
const int MAXN = 1000 + 5;
int _max, _min, _sum;
int setv[MAXN], minv[MAXN], maxv[MAXN], sumv[MAXN];
int n,m;
int pushdown(int o)//标记下传 
{	
	int lc = o*2, rc = o*2+1;
	if (setv[o]>=0)
	{
		setv[lc] = setv[rc] = setv[o];
		setv[o] = -1;
	}
}
int mt(int o, int l, int r)//更新结点信息 
{
	int m = (l+r)/2;
	int lc = o*2, rc = o*2+1;
	if (r>l)
	{
		sumv[o] = sumv[lc] + sumv[rc];
		minv[o] = min(minv[lc], minv[rc]);
		maxv[o] = max(maxv[lc], maxv[rc]);
	}
	if (setv[o]>=0) {minv[o]=maxv[o]=setv[o]; sumv[o] = setv[o]*(r-l+1);}
}
int x,y,v; 
int update(int o, int l, int r)//修改x,y区间值为v 
{
	int m = (l+r)/2;
	int lc = o*2, rc = o*2+1;
	if (x<=l&&r<=y) 
	{
	    setv[o] = v;
	} else
	{
		pushdown(o);
		if (x<=m) update(lc, l, m); else mt(lc, l, m);
		if (m<y) update(rc,m+1,r); else mt(rc, m+1,r);
	}
	mt(o,l,r);
} 
int query(int o, int l, int r)//查询x,y区间信息 
{
	int m = (l+r)/2;
	int lc = o*2, rc = o*2+1;
	if (setv[o]>=0)
	{
		_sum += setv[o] * (min(r,y)-max(l,x)+1);
		_min = min(_min, setv[o]);
		_max = max(_max, setv[o]);
	} else if (x<=l&&r<=y)
	{
		_sum += sumv[o];
		_min = min(_min, minv[o]);
		_max = max(_max, maxv[o]);
	} else
	{
		if (x<=m) query(lc, l, m);
		if (m<y) query(rc,m+1,r);
	}
}
int main()
{
    freopen("st.in", "r", stdin);freopen("st.out", "w", stdout);
    scanf("%d%d", &n, &m);
    ms(setv,-1);//负数说明没有set标记 
    for (int i=1;i<=n;i++)//输入初值 
    {
        scanf("%d", &v);
        x = y = i;
        update(1,1,n);
    }
    for (int i=1;i<=m;i++)//输入指令 
    {
        int type;
        scanf("%d", &type);
        if (type==0)
        {
           scanf("%d%d%d", &x, &y, &v);
           update(1,1,n);
        } else
        {
           scanf("%d%d", &x, &y);
           _sum=0;_max=-100000000; _min=100000000;
           query(1,1,n);
           switch(type)
           {
              case 1: printf("%d\n", _sum); break;
              case 2: printf("%d\n", _min); break;
              case 3: printf("%d\n", _max); break;
           }
        }
    }
    fclose(stdin);fclose(stdout);
    return 0;
}

4、混合多种修改,区间查询

(1) 修改+增加

input:
10 10
1 2 3 4 5 6 7 8 9 10
2 1 10
0 2 4 5
2 1 6
1 2 7 2
2 3 9
1 1 10 3
2 8 9
0 2 6 2
2 3 7
2 10 10
//n,m
//n个数,表示线段树初值
//m行,每行一个指令
//0 x y v          x,y区间值全部修改为v(v>=0)
//1 x y v          x,y区间值全部增加v
//2 x y            输出x,y区间最小值、最大值、区间和
output:
min:1 max:10 sum:55
min:1 max:6 sum:27
min:7 max:9 sum:55
min:11 max:12 sum:23
min:2 max:12 sum:20
min:13 max:13 sum:13
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ms(i,j) memset(i,j,sizeof i);
using namespace std;
const int MAXN = 1000 + 5;
int n,m; 
int _max, _min, _sum;
int maxv[MAXN], minv[MAXN], sumv[MAXN], addv[MAXN], setv[MAXN];
int mt(int o, int l, int r)
{
	int m = (l+r)/2;
	int lc = o*2, rc = o*2+1;
	sumv[o] = minv[o] = maxv[o] = 0;
	if (setv[o]>=0)
	{
		sumv[o] = setv[o]*(r-l+1); 
		minv[o] = maxv[o] = setv[o];
	}  else if (r>l)
	{
		sumv[o] = sumv[lc]+sumv[rc];
		minv[o] = min(minv[lc], minv[rc]);
		maxv[o] = max(maxv[lc], maxv[rc]);
	}
	if (addv[o]!=0) {sumv[o] += addv[o] * (r-l+1); minv[o] += addv[o]; maxv[o] += addv[o];}
}
int pushdown(int o)
{
	int lc = o*2, rc = o*2+1;
	if (setv[o]>=0)
	{
		setv[lc] = setv[rc] = setv[o];
		addv[lc] = addv[rc] = 0;//传下去一定要删除add标记 
		setv[o] = -1;
	}
	if (addv[o]!=0)
	{
		addv[lc] += addv[o]; 
		addv[rc] += addv[o]; 
		addv[o] = 0;
	}
}
int x,y,v;
int update_add(int o, int l, int r)
{
	int m = (l+r)/2;
	int lc = o*2, rc = o*2+1;
	if (x<=l&&r<=y) addv[o] += v;
	else
	{
		if (x<=m) update_add(lc, l, m); 
		if (m<y) update_add(rc, m+1, r);
	}
	mt(o,l,r);
}
int update_set(int o, int l, int r)
{
	int m = (l+r)/2;
	int lc = o*2, rc = o*2+1;
	if (x<=l&&r<=y) 
	{
		setv[o] = v;
		addv[o] = 0;
	}
	else
	{
		pushdown(o);
		if (x<=m) update_set(lc, l, m); else mt(lc, l, m);
		if (m<y) update_set(rc, m+1, r); else mt(rc, m+1, r);
	}
	mt(o,l,r);
}
int query(int o, int l, int r, int add)
{
	int m = (l+r)/2;
	int lc = o*2, rc = o*2+1;
	if (setv[o]>=0)
	{
		_sum += (setv[o]+add+addv[o])*(min(y,r)-max(x,l)+1);
		_min = min(_min, setv[o]+add+addv[o]);
		_max = max(_max, setv[o]+add+addv[o]);
	} else if (x<=l&&r<=y) 
	{
		_sum += sumv[o]+add*(r-l+1);
		_min = min(_min, minv[o]+add);
		_max = max(_max, maxv[o]+add);
	} else
	{
		if (x<=m) query(lc, l, m,add+addv[o]);
		if (m<y) query(rc, m+1, r,add+addv[o]);
	}
}
int main()
{
    freopen("st.in", "r", stdin);freopen("st.out", "w", stdout);
    scanf("%d%d", &n, &m);ms(setv, -1);
    for (int i=1;i<=n;i++)
    {
		scanf("%d", &v);
		x = y = i;
		update_set(1,1,n); 
	}
	for (int i=1;i<=m;i++)
	{
		int type;
		scanf("%d", &type);
		if (type==0)
		{
			scanf("%d%d%d", &x, &y, &v);
			update_set(1,1,n); 
		} else if (type==1)
		{
			scanf("%d%d%d", &x, &y, &v);
			update_add(1,1,n); 
		} else
		{
			scanf("%d%d", &x, &y);
			_sum=_max=0; _min=100000000;
			query(1,1,n,0);
			printf("min:%d max:%d sum:%d\n", _min, _max, _sum);
		}
	}
    fclose(stdin);fclose(stdout);
    return 0;
}
(2)增加+乘法

https://www.luogu.org/problem/show?pid=3373









  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值