洛谷题单【数据结构2-2】线段树与树状数组

P3372 【模板】线段树 1

思路

线段树是一种完全二叉树,父节点的值是两个子结点的值之和
线段树可用于区间修改、区间查询、求最值等

实现

来自Pecco的知乎文章

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 1e5 + 5;
ll tree[MAXN << 2], mark[MAXN << 2], n, m, A[MAXN];
//将标记向下传递 
void push_down(int p, int len)
{
    if (len <= 1) return;
    tree[p << 1] += mark[p] * (len - len / 2);
    mark[p << 1] += mark[p];
    tree[p << 1 | 1] += mark[p] * (len / 2);
    mark[p << 1 | 1] += mark[p];
    mark[p] = 0;
}
void build(int p = 1, int cl = 1, int cr = n)
{
	//到达叶结点 
    if (cl == cr) return void(tree[p] = A[cl]);
    int mid = (cl + cr) >> 1;
    build(p << 1, cl, mid);
    build(p << 1 | 1, mid + 1, cr);
    tree[p] = tree[p << 1] + tree[p << 1 | 1];
}
ll query(int l, int r, int p = 1, int cl = 1, int cr = n)
{
	//区间包含 
    if (cl >= l && cr <= r) return tree[p];
    //区间重叠但不包含,需要先将标记向下传递 
    push_down(p, cr - cl + 1);
    ll mid = (cl + cr) >> 1, ans = 0;
    if (mid >= l) ans += query(l, r, p << 1, cl, mid);
    if (mid < r) ans += query(l, r, p << 1 | 1, mid + 1, cr);
    return ans;
}
void update(int l, int r, int d, int p = 1, int cl = 1, int cr = n)
{
	//区间包含,直接更改该节点的值,更新标记 
    if (cl >= l && cr <= r) 
	{
		tree[p] += d * (cr - cl + 1), mark[p] += d;
		return ;
	}
    //区间重叠但不包含 
    push_down(p, cr - cl + 1);
    int mid = (cl + cr) >> 1;
    if (mid >= l) update(l, r, d, p << 1, cl, mid);
    if (mid < r) update(l, r, d, p << 1 | 1, mid + 1, cr);
    tree[p] = tree[p << 1] + tree[p << 1 | 1];
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        cin >> A[i];
    build();
    while (m--)
    {
        int o, l, r, d;
        cin >> o >> l >> r;
        if (o == 1)
            cin >> d, update(l, r, d);
        else
            cout << query(l, r) << '\n';
    }
    return 0;
}

P3374 【模板】树状数组 1

思路

前缀和维护的区间和是[1,Ai],而树状数组维护的区间和是(Ai-lowbit(Ai),Ai]
树状数组常用于区间查询和单点修改

实现

来自Pecco的知乎文章

#include <bits/stdc++.h>
using namespace std;
#define MAXN 500005
#define lowbit(x) ((x) & (-x))
int tree[MAXN];
int n,m;
inline void update(int i, int x)
{
    for (int pos = i; pos <= n; pos += lowbit(pos))
        tree[pos] += x;
}
inline int query(int n)
{
    int ans = 0;
    for (int pos = n; pos; pos -= lowbit(pos))
        ans += tree[pos];
    return ans;
}
inline int query(int a, int b)
{
    return query(b) - query(a - 1);
}
int main()
{
	cin>>n>>m;
	int t;
	memset(tree, 0, sizeof(tree));
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&t);
		update(i,t);
	}
	int a,b,c;
	for(int i=0;i<m;i++)
	{
		scanf("%d%d%d",&a,&b,&c);
		if(a==1)
			update(b,c);
		else
			printf("%d\n",query(b,c));
	}
    return 0;
}

P3368 【模板】树状数组 2

思路

树状数组用于区间修改和单点查询时,只要维护差分数组即可

实现

#include <bits/stdc++.h>
using namespace std;
#define MAXN 500005
#define lowbit(x) ((x) & (-x))
int tree[MAXN];
int a[MAXN];
int n,m;
inline void update(int i, int x)
{
    for (int pos = i; pos <= n; pos += lowbit(pos))
        tree[pos] += x;
}
inline int query(int n)
{
    int ans = 0;
    for (int pos = n; pos; pos -= lowbit(pos))
        ans += tree[pos];
    return ans;
}
int main()
{
	cin>>n>>m;
	int t;
	memset(tree, 0, sizeof(tree));
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	int op;
	int x,y,k;
	for(int i=0;i<m;i++)
	{
		scanf("%d",&op);
		if(op==1)
		{
			scanf("%d%d%d",&x,&y,&k);
			update(x,k);
			update(y+1,-k);
		}
		else
		{
			scanf("%d",&k);
			printf("%d\n",a[k]+query(k));
		}
	}
    return 0;
}

P2574 XOR的艺术

思路

和线段树的模板题相比,仅仅是标记的使用方法和更新方式发生了变化
标记变为bool值,每次与1异或
而tree数组的更新则是当标记值为1时,tree[p] = len-tree[p]

实现

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 200005;
int tree[MAXN << 2], mark[MAXN << 2], n, m, A[MAXN];
//将标记向下传递 
void push_down(int p, int len)
{
	if(!mark[p]) return ;
    if (len <= 1) return ;
    tree[p << 1] = (len - len / 2) - tree[p << 1];
    mark[p << 1] ^= 1;
    tree[p << 1 | 1] = (len / 2) - tree[p << 1 | 1];
    mark[p << 1 | 1] ^= 1;
    mark[p] = 0;
}
void build(int p = 1, int cl = 1, int cr = n)
{
	//到达叶结点 
    if (cl == cr) return void(tree[p] = A[cl]);
    int mid = (cl + cr) >> 1;
    build(p << 1, cl, mid);
    build(p << 1 | 1, mid + 1, cr);
    tree[p] = tree[p << 1] + tree[p << 1 | 1];
}
ll query(int l, int r, int p = 1, int cl = 1, int cr = n)
{
	//区间包含 
    if (cl >= l && cr <= r) return tree[p];
    //区间重叠但不包含,需要先将标记向下传递 
    push_down(p, cr - cl + 1);
    ll mid = (cl + cr) >> 1, ans = 0;
    if (mid >= l) ans += query(l, r, p << 1, cl, mid);
    if (mid < r) ans += query(l, r, p << 1 | 1, mid + 1, cr);
    return ans;
}

//[cl,cr]是当前树的区间,[l,r]是要操作的区间 
void update(int l, int r,int p = 1, int cl = 1, int cr = n)
{
	//区间包含,直接更改该节点的值,更新标记 
    if (cl >= l && cr <= r) 
	{
		tree[p] = (cr - cl + 1) - tree[p], mark[p] ^= 1;
		return ;
	}
    //区间重叠但不包含 
    if(mark[p])
    push_down(p, cr - cl + 1);
    
    int mid = (cl + cr) >> 1;
    if (mid >= l) update(l, r, p << 1, cl, mid);
    if (mid < r) update(l, r, p << 1 | 1, mid + 1, cr);
    tree[p] = tree[p << 1] + tree[p << 1 | 1];
}
int main()
{
//	freopen("P2574_1.in","r",stdin); 
//    ios::sync_with_stdio(false);
    scanf("%d %d\n",&n,&m);
    char ch;
    for (int i = 1; i <= n; ++i)
    {
    	ch = getchar();
    	A[i] = ch - '0';
	}
    build();
    while (m--)
    {
        int o, l, r;
        scanf("%d%d%d",&o,&l,&r);
        if (o == 0)
            update(l, r);
        else
            cout << query(l, r) << '\n';

    }
    return 0;
}

P3373 【模板】线段树 2

思路

  • 需要两个标记,一个代表加一个代表乘,同时也需要两个update函数
  • push_down的时候注意
    • 子结点的值 = 子结点的值 * 父结点的乘法标记 + 区间长度 * 父结点的加法标记
    • 子结点的乘法标记 *= 父节点的乘法标记
    • 子结点的加法标记 = 子结点的加法标记 * 父节点的乘法标记 + 子节点的加法标记
  • 同时所有的计算最后需要模p

实现

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 1e5 + 5;
ll tree[MAXN << 2], mark[MAXN << 2], mark2[MAXN << 2], n, m, P, A[MAXN];
//将标记向下传递 
void push_down(int p, int len)
{
    if (len <= 1) return;
    tree[p << 1] = (tree[p << 1] * mark2[p] + mark[p] * (len - len / 2)) % P;
    mark[p << 1] = (mark[p << 1] * mark2[p] + mark[p]) % P;
    mark2[p << 1] *= mark2[p];
    
    tree[p << 1 | 1] = (tree[p << 1 | 1] * mark2[p] + mark[p] * (len / 2)) % P;
    mark[p << 1 | 1] = (mark[p << 1 | 1] * mark2[p] + mark[p]) % P;
    mark2[p << 1 | 1] *= mark2[p];
    
	mark2[p << 1] %= P;
	mark2[p << 1 | 1] %= P;
	
    mark[p] = 0;
    mark2[p] = 1; 
    return ;
}
void build(int p = 1, int cl = 1, int cr = n)
{
	//到达叶结点 
    if (cl == cr) return void(tree[p] = A[cl]);
    int mid = (cl + cr) >> 1;
    build(p << 1, cl, mid);
    build(p << 1 | 1, mid + 1, cr);
	tree[p] = (tree[p << 1] + tree[p << 1 | 1]) % P;
}
ll query(int l, int r, int p = 1, int cl = 1, int cr = n)
{
	//区间包含 
    if (cl >= l && cr <= r) return tree[p];
    //区间重叠但不包含,需要先将标记向下传递 
    push_down(p, cr - cl + 1);
    ll mid = (cl + cr) >> 1, ans = 0;
    if (mid >= l) ans += query(l, r, p << 1, cl, mid);
    if (mid < r) ans += query(l, r, p << 1 | 1, mid + 1, cr);
    return ans;
}
void update(int l, int r, int d, int p = 1, int cl = 1, int cr = n)
{
	//区间包含,直接更改该节点的值,更新标记 
    if (cl >= l && cr <= r) 
	{
		tree[p] = (tree[p] + d * (cr - cl + 1)) % P, mark[p] += d, mark[p] %= P;
		return ;
	}
    //区间重叠但不包含 
    push_down(p, cr - cl + 1);
    int mid = (cl + cr) >> 1;
    if (mid >= l) update(l, r, d, p << 1, cl, mid);
    if (mid < r) update(l, r, d, p << 1 | 1, mid + 1, cr);
	tree[p] = (tree[p << 1] + tree[p << 1 | 1]) % P;
}

void update2(int l, int r, int d, int p = 1, int cl = 1, int cr = n)
{
	//区间包含,直接更改该节点的值,更新标记 
    if (cl >= l && cr <= r) 
	{
		tree[p] *= d, mark2[p] *= d, mark[p] *= d, tree[p] %= P, mark[p] %= P, mark2[p] %= P;
		return ;
	}
    //区间重叠但不包含 
    push_down(p, cr - cl + 1);
    int mid = (cl + cr) >> 1;
    if (mid >= l) update2(l, r, d, p << 1, cl, mid);
    if (mid < r) update2(l, r, d, p << 1 | 1, mid + 1, cr);
    tree[p] = (tree[p << 1] + tree[p << 1 | 1]) % P;
}

int main()
{
    ios::sync_with_stdio(false);
    cin >> n >> m >> P;
    for (int i = 1; i <= n; ++i)
        cin >> A[i];
    build();
//    cout<<"p: "<<P<<endl;
    for(int i = 0; i <= 2*n; ++i)
    	mark2[i] = 1;
    	
//    for(int i = 0; i <= 2*n; ++i)
//    	cout<<tree[i]<<" ";
//    	cout<<endl;
     	
    while (m--)
    {
        int o, l, r, d;
        cin >> o >> l >> r;
        if (o == 1)  //乘操作 
            cin >> d, update2(l, r, d);
        else if(o==2)  //加操作 
        	cin >> d, update(l, r, d);
        else
            cout << query(l, r) % P << '\n';
    }
    return 0;
}

P1908 逆序对

思路

【算法2-3】分治中提到的解法是归并排序,这道题也可以使用离散化+树状数组来做

实现

//来自洛谷题解区 学无止境
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int tree[500010],ranks[500010],n;
long long ans; 
struct point
{
    int num,val;
}a[500010];
inline bool cmp(point q,point w)
{
    if(q.val==w.val)
        return q.num<w.num;
    return q.val<w.val;
}
inline void insert(int p,int d)
{
    for(;p<=n;p+=p&-p)
        tree[p]+=d; 
}
inline int query(int p)
{
    int sum=0;
    for(;p;p-=p&-p)
        sum+=tree[p];
    return sum;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i].val),a[i].num=i;
    sort(a+1,a+1+n,cmp);
    for(int i=1;i<=n;i++)
        ranks[a[i].num]=i;
    for(int i=1;i<=n;i++)
    {
        insert(ranks[i],1);
        ans+=i-query(ranks[i]);
    }
    printf("%lld",ans);
    return 0;
} 

P1966 [NOIP2013 提高组] 火柴排队

思路

两列火柴中,每个位置的火柴在所在列中的长度排名都相同时, ∑ ( a i − b i ) 2 \sum(a_i-b_i)^2 (aibi)2最小
转化为逆序对问题

实现

#include<bits/stdc++.h>
using namespace std;
typedef struct{
	int v;
	int k;
}node;
int n;
node a[100010],b[100010];
int x[100010],c[100010];
long long ans;
void msort(int b,int e)
{
    if(b==e)  
		return;
    int mid=(b+e)/2,i=b,j=mid+1,k=b;
    msort(b,mid),msort(mid+1,e);
    while(i<=mid&&j<=e)
    {
    	if(x[i]<=x[j]) 
    		c[k++]=x[i++];  //c数组辅助归并排序
    	else  //左边比右边大,产生逆序对
    		c[k++]=x[j++],ans= (ans+mid-i+1) % (99999997);
    }
    while(i<=mid)  //如果左边没有放完就把左边区间所有剩下的数放进去
    	c[k++]=x[i++];
    while(j<=e)    //右边同理
    	c[k++]=x[j++];
    for(int l=b;l<=e;l++)  //更新a
    	x[l]=c[l];
} 
bool cmp(node a,node b)
{
	return a.v<b.v;
}
int main()
{
    scanf("%d",&n); 
    for(int i=1;i<=n;i++)
    {
		scanf("%d",&a[i].v);
		a[i].k = i;
	}
    
    for(int i=1;i<=n;i++)
    {
		scanf("%d",&b[i].v);
		b[i].k = i;
	}    
    
    sort(a+1,a+1+n,cmp);
    sort(b+1,b+1+n,cmp);
    
    for(int i=1;i<=n;i++)
    {
    	x[b[i].k] = a[i].k;
	}
    
    msort(1,n);
    printf("%lld",ans%(99999997));
    return 0;
} 

P5677 [GZOI2017]配对统计

思路

预处理出所有好对+树状数组
洛谷题解区一篇优秀的题解

实现

#include <bits/stdc++.h>
using namespace std;
#define MAXN 300005
#define lowbit(x) ((x) & (-x))
typedef long long ll;
ll tree[MAXN];
int n,m;
struct query{
	int l,r;
	int index;
}querys[MAXN];
struct node{
	int num;
	int pos;
}a[MAXN];
struct goodpair{
	int l,r;
}pairs[MAXN<<2];
int paircount;
void add_pair(int a,int b)
{
	int i;
	if(a>b) i = a,a = b,b = i;
	pairs[++paircount].l = a;
	pairs[paircount].r = b;
}
inline void update(int i, int x)
{
    for (int pos = i; pos <= n; pos += lowbit(pos))
        tree[pos] += 1;
}
inline ll Query(int n)
{
    ll ans = 0;
    for (int pos = n; pos; pos -= lowbit(pos))
        ans += tree[pos];
    return ans;
}
inline ll Query(int a, int b)
{
    return Query(b) - Query(a - 1);
}
bool cmp(struct node a,struct node b)
{
	return a.num<b.num;
}
bool cmp_pair(struct goodpair a,struct goodpair b)
{
	if(a.r!=b.r) return a.r<b.r;
	return a.l<b.l;
}
bool cmp_query(struct query a,struct query b)
{
	if(a.r!=b.r) return a.r<b.r;
	return a.l<b.l;	
}
int main()
{
	cin>>n>>m;
	if(n==1) 
	{cout<<"0";
	return 0;
}
	int t;
	memset(tree, 0, sizeof(tree));
	for(int i = 1; i <= n; ++i)
	{
		scanf("%d",&t);
		a[i].num = t;
		a[i].pos = i;
	}
	sort(a+1,a+1+n,cmp);
	for(int i = 1; i <= m; ++i)
	{
		scanf("%d%d",&querys[i].l,&querys[i].r);
		querys[i].index = i;
	}
	sort(querys+1,querys+1+m,cmp_query);
	if(n!=1)
	{
		add_pair(a[1].pos,a[2].pos);
		add_pair(a[n].pos,a[n-1].pos);
	}
	for(int i=2;i<n;i++)
	{
		int d1 = a[i].num-a[i-1].num;
		int d2 = a[i+1].num-a[i].num;
		if(d1==d2) add_pair(a[i].pos,a[i-1].pos),add_pair(a[i+1].pos,a[i].pos);
		else if(d1>d2) add_pair(a[i].pos,a[i+1].pos);
		else add_pair(a[i].pos,a[i-1].pos);
	}
	sort(pairs+1,pairs+1+paircount,cmp_pair);
	ll ans = 0;

	for(int i=1,j=1;i<=m;i++)
	{
		while(pairs[j].r<=querys[i].r && j<=paircount)
		{
			update(pairs[j].l,1);
			j++;
		}
		ans+=querys[i].index*(j-1-Query(querys[i].l-1));
	}
	printf("%lld",ans);
    return 0;
}

这部分的题好难 不想再写剩下的了= =

欢迎指正-

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值