2021年 广工第十五届文远知行杯 D-动态序列(Lazy_tag 线段树 区间加 区间乘 序列头尾加)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

题意:

在这里插入图片描述
本题涉及到 区间修改 单点修改 单点查询,因此需要 pushdown 函数,又由于没有 区间查询,因此无需 pushup

本题需要使用 带懒标记的线段树,想看讲解可以戳这:带懒标记的线段树讲解

思路:

因为有向 序列头部和尾部插入操作,所以我们要在 初始化 上做一下手脚,总共有 1e5 个数1e5 个操作询问,即可能为 1e5 个向前插入 或者 1e5 个向后插入

因此我们总共开 3e5 * 4 的空间(此时 存储线段树的结构体数组 t 则要开 3e5 * 4 * 4 的空间),从 1e5 + 11e5 + n初始序列 位置。

sted 标记分别表示 当前头尾,为了维护时候考虑到 先加后乘先乘后加 等顺序问题时,我们需要 推一下公式

设定 两个懒标记 mul(初始化为 1)和 add(初始化为 0),

  • 如果此时操作为 序列加 Xmodify 函数 op==2) 则 add += X 即可,

  • 如果此时操作为 序列乘 Xmodify 函数 op==1),则为 add *= Xmul *= X,因为真实数为 (a + add),此时乘 X 则可以变为 a * X + add * X

当然,在 pushdown 操作时候也需要注意到,当 mul 标记 向下传递 时候,下层原有的 add,也应该 *= root.mul

void pushdown(int u)		//标记下传(注意只对标记操作)
{
    auto &root = t[u], &left = t[u<<1], &right = t[u<<1|1];
    if(root.mul != 1 || root.add)
    {
    	// 对于左儿子 left 的 mul,直接累乘取模即可
        left.mul = left.mul * root.mul % mod;
		// 对于左儿子 left 的 add,显然要先在其原有的 add 基础上 乘其父节点 root 传递给它的 mul,之后再加上其父节点 root 传递给它的 add(注意取模)
        left.add = (left.add * root.mul % mod + root.add) % mod;	
        right.mul = right.mul * root.mul % mod;
        right.add = (right.add * root.mul % mod + root.add) % mod;
        root.mul = 1, root.add = 0;
    }
}

由于没有 区间查询,因此无需 pushup 函数,只需要 pushdown(本题要求实现 单点修改 区间修改 单点查询

时间复杂度:

O ( n l o g n ) O(nlogn) O(nlogn)

代码:

#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#include<bits/stdc++.h>

using namespace std;

//#define int long long
typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> pii;
typedef map<int, int> mi;
typedef unordered_map<int, int> umi;
typedef vector<ll> vll;
typedef pair<ll, ll> pll;
#define pb push_back
#define pp pop_back
#define x first
#define y second
const int N = 3e5 + 10, M = 1e5 + 10, mod = 1e9 + 7;
int a[N], n, q;
struct node
{
    int l, r;
    ll val, mul, add;
} t[N<<2];

void build(int u, int l, int r)
{
    t[u] = {l, r}, t[u].mul = 1, t[u].add = 0;
    if(l==r)
    {
        t[u].val = a[l];
        return ;
    }
    
    int mid = l + r >> 1;
    build(u<<1, l, mid), build(u<<1|1, mid+1, r);
}

void pushdown(int u)	//标记下传
{
    auto &root = t[u], &left = t[u<<1], &right = t[u<<1|1];
    if(root.mul != 1 || root.add)
    {
        left.mul = left.mul * root.mul % mod;
        left.add = (root.mul * left.add % mod + root.add) % mod;	//
        right.mul = right.mul * root.mul % mod;
        right.add = (root.mul * right.add % mod + root.add) % mod;
        root.mul = 1, root.add = 0;
    }
}

ll ask(int u, ll x)		//查询第 x 个元素
{
    if(t[u].l==x && t[u].r==x)
    {
        t[u].val = (t[u].val * t[u].mul % mod + t[u].add) % mod;
        t[u].mul = 1, t[u].add = 0;		//一定记得这里也要清除标记
        return t[u].val;
    }
    
    pushdown(u);
    int mid = t[u].l + t[u].r >> 1;
    if(x<=mid) return ask(u<<1, x);
    else return ask(u<<1|1, x);
}

void modify(int u, int l, int r, ll b, int op)		//区间修改(区间乘 区间加 先只对标记进行修改处理)
{
    if(l<=t[u].l && r>=t[u].r)
    {
        if(op==1)	//区间乘
        {
            t[u].mul = t[u].mul * b % mod;
            t[u].add = t[u].add * b % mod;
        }
        else t[u].add = (t[u].add + b) % mod;	//区间加
        return ;
    }
    
    pushdown(u);
    int mid = t[u].l + t[u].r >> 1;
    if(l<=mid) modify(u<<1, l, r, b, op);
    if(r>=mid+1) modify(u<<1|1, l, r, b, op);
}

void change(int u, int x, ll v)		//单点修改(头尾添加)
{
    if(t[u].l==x && t[u].r==x) {t[u].val = v; return ;}
    
    int mid = t[u].l + t[u].r >> 1;
    if(x<=mid) change(u<<1, x, v);
    else change(u<<1|1, x, v);
}

signed main()
{
    int t; //cin>>t;
    t = 1;
        
    while(t--)
    {
        scanf("%d%d", &n, &q);
        for(int i=1+M; i<=n+M; ++i) scanf("%d", &a[i]);
            
        int st = 1 + M, ed = n + M;
            
        build(1, 1, N);
            
        while(q--)
        {
            int op; ll b;
            scanf("%d%lld", &op, &b);
            if(op==5) printf("%lld\n", ask(1, st + b - 1));
            else if(op==1 || op==2)
            {
                modify(1, st, ed, b, op);
            }
            else
            {
                if(op==3) --st, change(1, st, b);
                else ++ed, change(1, ed, b);
            }
        }
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值