线段树应用专题

区间更新加区间查询模板

题目POJ3468

ac代码

#include<iostream>
#define ls idx<<1
#define rs idx<<1|1
using namespace std;

typedef long long ll;
const int maxn=1e5+5;

ll sum[maxn<<2],tag[maxn<<2];
int num[maxn];

inline void pushup(ll idx)
{
    sum[idx]=sum[ls]+sum[rs];
}
inline void pushdown(ll idx,ll l,ll r)
{
    ll mid=l+r>>1;
    tag[ls]+=tag[idx];
    sum[ls]+=tag[idx]*(mid-l+1);
    tag[rs]+=tag[idx];
    sum[rs]+=tag[idx]*(r-mid);
    tag[idx]=0;
}
void buildtree(ll idx,ll l,ll r)
{
    if(l==r)
    {
        sum[idx]=num[l];
        tag[idx]=0;
        return;
    }
    ll mid=l+r>>1;
    buildtree(ls,l,mid);
    buildtree(rs,mid+1,r);
    pushup(idx);
}

ll query(ll idx,ll l,ll r,const ll& x,const ll& y)
{
    if(x<=l && y>=r)
        return sum[idx];
    if(tag[idx])
        pushdown(idx,l,r);
    ll res=0;
    ll mid=l+r>>1;
    if(x<=mid)
        res+=query(ls,l,mid,x,y);
    if(y>mid)
        res+=query(rs,mid+1,r,x,y);
    return res;
}
//区间加
void update(ll idx,ll l,ll r,const ll &x,const ll &y,const ll& val)
{
    if(x<=l && y>=r)
    {
        sum[idx]+=val*(r-l+1);
        tag[idx]+=val;
        return;
    }
    if(tag[idx])
        pushdown(idx,l,r);
    ll mid=l+r>>1;
    if(x<=mid)
        update(ls,l,mid,x,y,val);
    if(y>mid)
        update(rs,mid+1,r,x,y,val);
    pushup(idx);
}
//多样里时记得初始化
void init()
{
    memset(tag,0,sizeof(tag));
}
int main()
{
    ll N,M;
    scanf("%lld%lld",&N,&M);
    for (ll i = 1; i <= N; i++)
        scanf("%d", num + i);
    buildtree(1,1,N);
    while (M--){
        char c;
        //cin>>c;
        getchar();
        scanf("%c",&c);
        //求和指令
        if (c == 'Q'){
            ll xx, yy;
            scanf("%lld%lld", &xx, &yy);
            ll ans=query(1,1,N,xx, yy);
            printf("%lld\n", ans);
        }
        //区间更新
        else if (c == 'C'){
            ll xx, yy, zz;
            scanf("%lld%lld%lld", &xx, &yy, &zz);
            update(1,1,N,xx, yy, zz);
        }
    }
    return 0;
}

线段树+离散化

题目:POJ2528

题意和题解

题意:
给出一面墙,给出n张海报贴在墙上,每张海报都覆盖一个范围,问最后可以看到多少张海报
题解
海报覆盖的范围很大,直接使用数组存不下,
但是只有最多10000张海报,也就是说最多出现20000个点,所以可以使用离散化,
将每个点离散后,重新对给出控制的区间,这样区间最大就是1到20000.
可以直接使用线段树,成段更新,每次更新一个颜色,最后遍历所有的段,将还可以遍历的颜色储存,统计。
线段的离散化,要在两个差值大于1节点间,多加一个节点,代表这两点不连续。
但是这题数据有问题,没有加隔离点的离散化也可以过。

如 1 3  1 10  1 4  7 10 这种组样例  
正确结果应该是还可以看到3种颜色,但是如果直接排列点的话,不在距离大于1的点直接加如间隔点,
就会挤掉5到6这一种颜色,导致只能看到两种颜色。这显然是错误的离散化,但依然可以通过这题。

真正的这种成段的离散化,一定要将两个差值大于1节点中,多加一个节点,代表这两点不连续。

AC代码

/*
 
*/
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const int maxn = 1e5 + 5;
struct seg
{
    int l, r;
    seg(int L = 0, int R = 0)
    {
        l = L;
        r = R;
    }
} Seg[maxn];

int num[maxn << 2];
bool visit[maxn << 1];
int cou;
struct node
{
    int l, r, v;
    node(int L = 0, int R = 0, int V = 0)
    {
        l = L, r = R, v = V;
    }
} Node[maxn * 8];

void butre(int l, int r, int idx)
{
    Node[idx] = node(l, r);
    if (l == r)
        return;
    int mid = l + r >> 1;
    butre(l, mid, idx << 1);
    butre(mid + 1, r, idx << 1 | 1);
}

void qurry(int l, int r, int idx)
{
    if (Node[idx].v)
    {
        //cout<<l<<" oo "<<r<<" oo "<<Node[idx].v<<endl;
        if (!visit[Node[idx].v])
            cou++;
        visit[Node[idx].v] = true;
        return;
    }
    if (l == r)
        return;
    int mid = l + r >> 1;
    qurry(l, mid, idx << 1);
    qurry(mid + 1, r, idx << 1 | 1);
}
void pushdown(int idx)
{
    Node[idx << 1 | 1].v = Node[idx << 1].v = Node[idx].v;
    Node[idx].v = 0;
}
void update(int l, int r, int idx, int X)
{
    int L = Node[idx].l, R = Node[idx].r;

    if (l == L && r == R)
    {
        Node[idx].v = X;
        return;
    }
    if (Node[idx].v)
        pushdown(idx);
    int mid = L + R >> 1;
    if (l <= mid && r > mid)
    {
        update(l, mid, idx << 1, X);
        update(mid + 1, r, idx << 1 | 1, X);
    }
    else if (r <= mid)
        update(l, r, idx << 1, X);
    else if (l > mid)
        update(l, r, idx << 1 | 1, X);
}

int main()
{
    int t;
    cin >> t;
    while (t--)
    {
        memset(visit, 0, sizeof(visit));
        cou = 0;
        int cnt = 0;
        int n;
        cin >> n;
        for (int i = 1; i <= n; i++)
        {
            int l, r;
            cin >> l >> r;
            num[++cnt] = l;
            num[++cnt] = r;
            num[++cnt] = l + 1;
            Seg[i] = seg(l, r);
        }
        sort(num + 1, num + cnt + 1);
        int ncnt = cnt;
        //加入间隔点
        for (int i = 1; i < cnt; i++)
            if (num[i + 1] - num[i] > 1)
                num[++ncnt] = num[i] + 1;

        cnt = ncnt;
        sort(num + 1, num + 1 + cnt);
        cnt = unique(num + 1, num + cnt + 1) - num - 1;
        butre(1, cnt, 1);
        for (int i = 1; i <= n; i++)
        {
            int L = lower_bound(num + 1, num + cnt + 1, Seg[i].l) - num;
            int R = lower_bound(num + 1, num + cnt + 1, Seg[i].r) - num;
            update(L, R, 1, i);
        }
        qurry(1, cnt, 1);
        cout << cou << endl;
    }
    return 0;
}

线段树上二分查询

题目POJ2828

题意+题解

题意:
给出n个人入队位置,输入一个idx,num
代表num需要插入队列中第idx个人后面
请输出所有入队后的队列情况

题解:
对于第n个人而言,他的位置是可以直接确定的,即idx[n]+1,
而对于第n-1个人,假设前n-2个人的位置已经排好,然后把他放在了idx[n-1]+1的位置,
他位置只可能会被第n个人影响,即当第n个人插入再第n-1个人之前时,他的位置编号变成了idx[n-1]+2
同理对于第n-2个人,假设前n-3的位置已经确定,然后把他放在了idx[n-2]+1的位置,
那么的他编号只可能因为第n-1个人或者第n个插入在idx[n-2]+1之前时发生改变

总结来说:任意一个人的下标只可能会被在他后面插入的人而改变。

对于最终的状态,即所有人都已经在对应的位置上,设此时第i个人的位置是loc[i]
可以发现,idx[i]+1=loc[i]-(编号为i+1~n的人中,最终排第i个人前面的人的人数)
那么,对编号小于等于i的人设他的贡献为1,对于编号大于i的人设他的贡献0,
可以用一个数列将最终队列状态中,对于第i个人的贡献状态表示出来
前缀和等于idx[i]+1的位置即为loc[i]

通过前面的讨论,可以知道,对于第n个人所有人都是有贡献的,其实即为一个全1的数列,
找到前缀和为idx[n]+1的位置,
然后将idx[n]+1这个位置的贡献置为0,去讨论第n-1个人,找到前缀和为idx[n-1]+1的位置,
重复上述过程

可以通过线段树维护一个初始全为1的数列,然后不断进行单点修改以及二分查询前缀和模拟操作,
然而因为这两个操作都是同一个位置,所以直接合并

AC代码

/*

*/

#include<iostream>
#include<stdio.h>
#define ls idx<<1
#define rs idx<<1|1

using namespace std;
const int maxn=2e5+5;

int ans[maxn];

int sum[maxn<<2];
inline void pushup(int idx) {sum[idx]=sum[rs]+sum[ls];}
void build(int idx,int l,int r)
{
    if(l==r)
    {
        sum[idx]=1;
        return ;
    }
    int mid=l+r>>1;
    build(ls,l,mid);
    build(rs,mid+1,r);
    pushup(idx);
    //cout<<idx<<' '<<l<<' '<<r<<'\n';
}

int query_change(int idx,int l,int r,int num)
{
    if(l==r)
    {
        sum[idx]=0;
        return l;
    }
    int res;
    int mid=l+r>>1;
    if(sum[ls]>=num)
        res=query_change(ls,l,mid,num);
    else
        res=query_change(rs,mid+1,r,num-sum[ls]);
    pushup(idx);
    return res;
}

int id[maxn],Num[maxn];
int main()
{
    //std::ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int n;
    while (~scanf("%d",&n))
    {
        //cout<<n<<endl;
        build(1,1,n);
        for(int i=1;i<=n;i++)
            scanf("%d%d",id+i,Num+i);
        for(int i=n;i>=1;i--)
            ans[query_change(1,1,n,id[i]+1)]=Num[i];
        for(int i=1;i<n;i++)
            printf("%d ",ans[i]);
        printf("%d\n",ans[n]);
    }
    
    return 0;
}

线段树上找答案

题目HDU1540

题意+题解

题意:
    给出n个一排的城市,初始相邻城市相互连接
    有3个操作
    D x:损坏城市x的管道,使得他与相邻城市不相连
    Q x:查询包含x城市的最长连续城市段
    R  :修复最近损坏的一个城市的管道
题解:
    用0表示损坏,1表示为损坏。那么问题转化为k位置所在最长‘1’串
    考虑用线段树维护区间最长连续1串
    那么只需要维护每个区间的前缀连续1,后缀连续1,以及最长连续1,即可实现区间合并
    那么我们怎么知道,该区间的最长连续1是否包含了位置k呢
    注意到任意一个区间的最长连续串1,必定是由某些子区间的前缀1和后缀1拼接得到的
    这就给查询提供了思路。
    当查询到k落于该区间的[mid-pre[ls]+1,mid+pre[rs]]这段区间时,将这段区间返回即可
    否则继续查询包含k的子区间
    经过上述讨论发现,查询k只需要知道每个区间的前缀1,和后缀1即可
    前缀1和后缀1的合并方法也很显然,画图即可知,具体见实现
    修改就是单点修改

AC代码

/*

*/

#include <bits/stdc++.h>
#define ls idx << 1
#define rs idx << 1 | 1
using namespace std;
const int maxn = 5e4 + 5;

int pre[maxn << 2], suf[maxn << 2];
void pushup(int idx, int l, int r)
{
    pre[idx] = pre[ls];
    suf[idx] = suf[rs];
    int mid = l + r >> 1;
    if (pre[ls] == mid - l + 1)
        pre[idx] += pre[rs];
    if (suf[rs] == r - mid)
        suf[idx] += suf[ls];
}
void build(int idx, int l, int r)
{
    if (l == r)
    {
        pre[idx] = suf[idx] = 1;
        return;
    }
    int mid = l + r >> 1;
    build(ls, l, mid);
    build(rs, mid + 1, r);
    pushup(idx, l, r);
}

int query(int idx, int l, int r, int k)
{
    if (l == r)
        return pre[idx];
    int mid = l + r >> 1;
    if (k >= mid - suf[ls] + 1 && k <= mid + pre[rs])
        return suf[ls] + pre[rs];
    else if (k <= mid)
        return query(ls, l, mid, k);
    else
        return query(rs, mid + 1, r, k);
}

void update(int idx, int l, int r, const int x, const int val)
{
    if (l == r && l == x)
    {
        pre[idx] = suf[idx] = val;
        return;
    }
    int mid = l + r >> 1;
    if (x <= mid)
        update(ls, l, mid, x, val);
    else
        update(rs, mid + 1, r, x, val);
    pushup(idx, l, r);
}
int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, m;
    stack<int> ST;
    while (cin >> n >> m)
    {
        build(1, 1, n);
        while (!ST.empty())
            ST.pop();
        while (m--)
        {
            char c;
            int x;
            cin >> c;
            if (c == 'D')
            {
                cin >> x;
                ST.push(x);
                update(1, 1, n, x, 0);
            }
            else if (c == 'Q')
            {
                cin >> x;
                cout << query(1, 1, n, x) << '\n';
            }
            else
            {
                if (ST.size())
                {
                    update(1, 1, n, ST.top(), 1);
                    ST.pop();
                }
            }
        }
    }
    return 0;
}

线段树维护树上信息

题目:POJ3321

题意+题解

题意:
给你一棵树,每个节点开始的时候有一个苹果,
下边m个操作,Q a,查询以a和a的子树的总共的苹果数,
c b,修改操作,改变b节点的苹果状态,有变没有,没有变成有

题解:
通过求每个节点的DFS序得出每棵子树的区间表示法,
然后通过线段树/树状数组进行单点修改和区间查询即可

AC代码

/*

*/
#include<iostream>
#include<vector>
#define ls idx<<1
#define rs idx<<1|1
using namespace std;
const int maxn=1e5+5;
int L[maxn],R[maxn];

vector<vector<int> > E(maxn);
int tot;
void dfs(int fa,int x)
{
    L[x]=++tot;
    for(int i=0;i<E[x].size();i++)
    {
        int v=E[x][i];
        if(v!=fa)
            dfs(x,v);
    }
    R[x]=tot;
}

int sum[maxn<<2];
inline void pushup(int idx)
{
    sum[idx]=sum[ls]+sum[rs];
}
void build(int idx,int l,int r)
{
    if(l==r)
    {
        sum[idx]=1;
        return;
    }
    int mid=l+r>>1;
    build(ls,l,mid);
    build(rs,mid+1,r);
    pushup(idx);
}

void change(int idx,int l,int r,int x)
{
    if(l==r && l==x)
    {
        sum[idx]^=1;
        return;
    }
    int mid=l+r>>1;
    if(x<=mid)
        change(ls,l,mid,x);
    else
        change(rs,mid+1,r,x);
    pushup(idx);
}

int query(int idx,int l,int r,const int x,const int y)
{
    if(x<=l && y>=r)
        return sum[idx];
    int mid=l+r>>1;
    int res=0;
    if(x<=mid)
        res+=query(ls,l,mid,x,y);
    if(y>mid)
        res+=query(rs,mid+1,r,x,y);
    return res;
}
int main()
{
    int n;
    //std::ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    while(~scanf("%d",&n)){
        
        tot=0;
        for(int i=1;i<n;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            E[x].push_back(y);
            E[y].push_back(x);
        }
        dfs(1,1);
        build(1,1,tot);
        int Q;
        scanf("%d",&Q);
        while (Q--)
        {
            getchar();
            char c;
            int xx;
            scanf("%c %d",&c,&xx);
            if(c=='Q')
                cout<<query(1,1,tot,L[xx],R[xx])<<'\n';
            else
                change(1,1,tot,L[xx]);
        }
        for(int i=1;i<=n;i++)
        E[i].clear();
    }
    
    return 0;
}

线段树优化建图

题目:CF7868-legacy

题意+题解

题意:
有n个点, q次操作, 操作有三种:
1 u v w 表示u -> v 有有一条权值为w的边.
2 u l r w 表示 u 到 下标在[l, r] 的城市 有一条权值为w的边
3 u l r w 表示 下标在[l, r] 的城市 到 u 有一条权值为w的边
然后求给出起点s, 输出s到每一个点的最小花费. 不能到达输出-1.

思路:
很明显直接暴力建边, 建的过程就会T, 边数也非常多.
但是,我们可以发现连的城市是一段连续区间,
所以我们需要利用这个特性, 那么就很明显想到线段树区间分层问题.
这样我们的建图的边数最多只有nlogn条边, 建边的过程也优化了.
具体操作过程为: 我们需要两颗线段树. 第一颗叫入树, 第二颗叫出树,
入树的每段区间都向左右儿子连一条权值为0的边, 叶子节点即为原来的点,
然后出树相反, 是左右儿子向父节点连一条权值为0的边, 叶子结点与入树共用. 都是有向边.
然后对于操作, 1操作直接连点加权边,
2操作u 向 入树对应区间的所有标号连一条权值为w的有向边,
3操作 出树的所有区间标号向u连一条权值为w的有向边.
这样就优化完成了, 并且符合要求, 画出来就很明显可以看出来了
最后跑堆优化的迪杰斯特拉即可得到答案

AC代码

/*

*/
#include <bits/stdc++.h>
#define ls idx << 1
#define rs idx << 1 | 1
using namespace std;

typedef long long ll;
const int maxn = 1e5 + 5;
int val[2][maxn << 2]; //每个区间的都是一个点,这是它们的编号
int cnt_ponit;

struct edge
{
    ll to, v;
    bool operator <(const edge &A) const
    {
        return v > A.v;
    }
};

vector<edge> E[maxn << 4];
void build(int idx, int l, int r, int flag)
{
    if (l == r)
    {
        val[flag][idx] = l;
        return;
    }
    val[flag][idx] = ++cnt_ponit;
    int mid = l + r >> 1;
    build(ls, l, mid, flag);
    build(rs, mid + 1, r, flag);
    if (!flag)
    {
        E[val[flag][idx]].push_back({val[flag][ls], 0});
        E[val[flag][idx]].push_back({val[flag][rs], 0});
    }
    else
    {
        E[val[flag][ls]].push_back({val[flag][idx], 0});
        E[val[flag][rs]].push_back({val[flag][idx], 0});
    }
}
//z向[x,y]区间连线(或者反向),权值为w,flag=0正向,flag=1反向
void query(int idx, int l, int r, const int x, const int y, const int z, const int w, const int flag)
{
    if (x <= l && y >= r)
    {
        if (!flag)
            E[z].push_back({val[flag][idx], w});
        else
            E[val[flag][idx]].push_back({z, w});
        return;
    }
    int mid = l + r >> 1;
    if (x <= mid)
        query(ls, l, mid, x, y, z, w, flag);
    if (y > mid)
        query(rs, mid + 1, r, x, y, z, w, flag);
}

priority_queue<edge> Q;
ll dis[maxn << 4];
bool vis[maxn << 4];
void DJ(int star)
{
    dis[star]=0;
    Q.push({star, 0});
    while (!Q.empty())
    {
        int now = Q.top().to;
        Q.pop();
        if (vis[now])
            continue;
        vis[now] = 1;
        for (int i = 0; i < E[now].size(); i++)
        {
            int to = E[now][i].to, v = E[now][i].v;
            if (dis[to] > dis[now] + v)
            {
                dis[to] = dis[now] + v;
                Q.push({to, dis[to]});
            }
        }
    }
}
int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, q, s;
    cin >> n >> q >> s;
    
    cnt_ponit = n;
    build(1, 1, n, 0);
    build(1, 1, n, 1);
    fill(dis+1, dis + cnt_ponit+1, 1e18);
    while (q--)
    {
        int op;
        cin >> op;
        int xx, yy, zz, ww;
        if (op == 1)
        {
            cin >> xx >> yy >> zz;
            E[xx].push_back({yy, zz});
        }
        else if (op == 2 || 3)
        {
            cin >> zz >> xx >> yy >> ww;
            query(1, 1, n, xx, yy, zz, ww, op - 2);
        }
        
        
    }
    for(int i=1;i<=cnt_ponit;i++)
    {
        E[i].push_back({i,0});
    }
    DJ(s);
    for (int i = 1; i <= n; i++)
    {
        if (dis[i] < 1e18)
            cout << dis[i] << ' ';
        else
            cout << -1 << ' ';
    }
    return 0;
}

线段树离线查询区间种类数

题目HDU3333

题意+题解

题意:
给出n个数a1,a2…an,
Q个询问,每个询问给出l,r
求区间[al~ar]出现过的数的种类和

题解:
线段树离线处理询问。
预处理处理出每个数上一次(数组中当前下标往前)出现的位置,
用lastidx[i]表示a[i]上一次出现的位置
对于每个询问(l,r),if(lastidx[i]<l)则a[i]是第一次在此区间出现,否则不是
把a[i]按lastidx[i]进行排序;
对所有区间按l进行排序,每次把lastidx[i]<l的a[i]加入线段树中,
然后询问线段树中区间(l,r)之和即可

AC代码

/*

*/

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

const int maxn = 1e5 + 10;
const int maxm = maxn << 2;
typedef long long ll;

//当前结点左右区间,左右孩子的下标,保存的区间和
int l[maxm], r[maxm], ls[maxm], rs[maxm];
ll val[maxm];

struct questions
{
    int l, r, id;
};

vector<questions> ques;
int a[maxn], lastidx[maxn];
int a_idx[maxn];
map<int, int> lef;
int n;

bool cmp(int aa, int bb)
{
    return lastidx[aa] < lastidx[bb];
}
bool cmp2(questions A, questions B)
{
    return A.l < B.l;
}
void pushup(int idx)
{
    val[idx] = val[rs[idx]] + val[ls[idx]];
}
void creat_tree(int idx, int x, int y)
{
    ls[idx] = idx<<1;
    rs[idx] = idx<<1|1;
    l[idx] = x, r[idx] = y;
    if (x == y)
    {
        val[idx] = 0;
        return;
    }
    int mid = x + y >> 1;
    creat_tree(ls[idx], x, mid);
    creat_tree(rs[idx], mid + 1, y);
    pushup(idx);
}

void add(int idx, const int &x)
{
    if (l[idx] == x && r[idx] == x)
    {
        val[idx] += a[x];
        return;
    }
    int mid = l[idx] + r[idx] >> 1;
    if (x <= mid)
        add(ls[idx], x);
    else
        add(rs[idx], x);
    pushup(idx);
}

ll query(int idx, const int x, const int y)
{
    if (x<=l[idx] && y >= r[idx])
        return val[idx];
    int mid = r[idx] + l[idx] >> 1;
    ll ans = 0;
    if (x <= mid)
        ans += query(ls[idx], x, y);
    if (y > mid)
        ans += query(rs[idx], x, y);
    return ans;
}

void init()
{
    for (int i = 1; i <= n; i++)
        a_idx[i] = i;
    ques.clear();
    lef.clear();
    for (int i = 1; i <= n; i++)
    {
        if (!lef.count(a[i]))
            lastidx[i] = 0;
        else
            lastidx[i] = lef[a[i]];
        lef[a[i]] = i;
    }
}
ll ans[maxn];
void sovle()
{
    
    sort(a_idx + 1, a_idx + 1 + n, cmp);//把a数组原来的下标按照lastidx[i]排序
    sort(ques.begin(), ques.end(),cmp2);
    int now=1;
    for(auto it : ques)
    {
        while(lastidx[a_idx[now]]<it.l && now<=n)
        {
            add(1,a_idx[now]);
            now++;
        }
        ans[it.id]=query(1,it.l,it.r);
    }

}

int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int T;
    cin >> T;
    while (T--)
    {
        cin >> n;
        for (int i = 1; i <= n; i++)
            cin >> a[i];
        init();
        creat_tree(1,1,n);
        int Q;
        cin >> Q;
        for (int i = 1; i <= Q; i++)
        {
            int x, y;
            cin >> x >> y;
            ques.push_back({x, y, i});
        }
        sovle();
        for(int i=1;i<=Q;i++)
            cout<<ans[i]<<'\n';
        //cout<<ans[Q]<<'\n';
    }
    return 0;
}

线段树维护哈希值

题目大意:给出一个长度为 n 的序列,接下来有 m 次操作,每次操作分为下列两种类型:

1 l r:区间 [ l , r ] 内的所有数都加 1 并对 65536 取模,也就是 i ∈ [ l , r ] ,有 a[ i ] =( a[ i ] + 1 ) % 65536
2 x y len:查询两段区间 [ x , x + len - 1 ] 和 [ y , y + len - 1 ] 内的序列是否相同

#include <bits/stdc++.h>

#define ls idx << 1
#define rs idx << 1 | 1
using namespace std;

typedef long long ll;
const ll maxn = 5e5 + 5, base = 10, mod = 1e9 + 7, mod2 = 65536;

ll tree[maxn << 2], L[maxn << 2], R[maxn << 2], mx[maxn << 2], tag[maxn << 2];
ll bpow[maxn]; //base的幂次
ll a[maxn];
ll sump[maxn]; //base幂次的前缀和

void pushup(ll idx)
{
    tree[idx] = (tree[rs] + (tree[ls] * bpow[R[rs] - L[rs] + 1])) % mod;
    mx[idx] = max(mx[ls], mx[rs]);
}

void pushdown(ll idx)
{
    tree[ls] = (tree[ls] + (sump[R[ls] - L[ls]]) * tag[idx]) % mod;
    tree[rs] = (tree[rs] + (sump[R[rs] - L[rs]]) * tag[idx]) % mod;
    tag[ls] = (tag[ls] + tag[idx]) % mod;
    tag[rs] = (tag[rs] + tag[idx]) % mod;
    mx[ls] = (mx[ls] + tag[idx]) % mod;
    mx[rs] = (mx[rs] + tag[idx]) % mod;
    tag[idx] = 0;
}
void build(ll idx, ll l, ll r)
{
    L[idx] = l, R[idx] = r;
    tag[idx] = 0;
    if (l == r)
    {
        mx[idx] = tree[idx] = a[l];
        return;
    }
    ll mid = l + r >> 1;
    build(ls, l, mid);
    build(rs, mid + 1, r);
    pushup(idx);
}

void update(ll idx, const ll &x, const ll &y, const ll &val)
{
    if (x <= L[idx] && y >= R[idx])
    {
        tag[idx] = (tag[idx] + val) % mod;
        tree[idx] = (tree[idx] + (sump[R[idx] - L[idx]])) % mod;
        mx[idx] = ll(mx[idx] + val) % mod;
        return;
    }
    if (tag[idx])
        pushdown(idx);
    ll mid = L[idx] + R[idx] >> 1;
    if (x <= mid)
        update(ls, x, y, val);
    if (y > mid)
        update(rs, x, y, val);
    pushup(idx);
}

void modify(ll idx)
{
    if (L[idx] == R[idx])
    {
        mx[idx] %= mod2;
        tree[idx] %= mod2;
        return;
    }
    if (tag[idx])
        pushdown(idx);
    if (mx[ls] >= mod2)
        modify(ls);
    if (mx[rs] >= mod2)
        modify(rs);
    pushup(idx);
}

ll query(ll idx, ll x, ll y)
{
    if (x <= L[idx] && y >= R[idx])
        return tree[idx] * bpow[(y - R[idx])];
    if (tag[idx])
        pushdown(idx);
    ll mid = L[idx] + R[idx] >> 1;
    ll ans = 0;
    if (x <= mid)
        ans += query(ls, x, y);
    if (y > mid)
        ans += query(rs, x, y);
    return ans % mod;
}
void init()
{

    bpow[0] = 1;
    for (ll i = 1; i < maxn; i++)
        bpow[i] = (bpow[i - 1] * base) % mod;
    sump[0] = 1;
    for (ll i = 1; i < maxn; i++)
        sump[i] = (sump[i - 1] + bpow[i]) % mod;
}
int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    init();
    ll n, q;
    cin >> n >> q;
    for (ll i = 1; i <= n; i++)
        cin >> a[i];
    build(1, 1, n);
    while (q--)
    {
        ll tp, x, y, LL;
        cin >> tp;
        if (tp == 1)
        {
            cin >> x >> y;
            update(1, x, y, 1);
            modify(1);
        }
        else
        {
            cin >> x >> y >> LL;
            //cout << query(1, x, x + LL - 1) << " q " << query(1, y, y + LL - 1) << '\n';
            if (query(1, x, x + LL - 1) == query(1, y, y + LL - 1))
                cout << "yes\n";
            else
                cout << "no\n";
        }
    }

    return 0;
}

动态开点线段树求逆序对

#include <bits/stdc++.h>

using namespace std;
const int maxn = 5e5 + 5;
const int inf = 1e9 + 5;
int a[maxn];
int tot = 1;
//查询逆序对只需要查询大于他的数的个数 
struct node
{
    int cnt, ls, rs;
} tree[maxn << 4];

void pushup(int rt)
{
    tree[rt].cnt = tree[tree[rt].ls].cnt + tree[tree[rt].rs].cnt;
}

void insert(int &rt, int l, int r, int val)
{
    //cout << l << ' ' << r << ' ' << val << " ins\n";
    if (!rt)
        rt = ++tot;
    if (l == r)
    {
        tree[rt].cnt++;
        
        return;
    }
    int mid = l + r >> 1;
    if (val <= mid)
        insert(tree[rt].ls, l, mid, val);
    else
        insert(tree[rt].rs, mid + 1, r, val);
    pushup(rt);
}

int query(int rt, int l, int r, int x, int y)
{
    if (!rt)
        return 0;
    if (x <= l && y >= r)
        return tree[rt].cnt;
    int mid = l + r >> 1;
    int ans = 0;
    if (x <= mid)
        ans += query(tree[rt].ls, l, mid, x, y);
    if (y > mid)
        ans += query(tree[rt].rs, mid + 1, r, x, y);
    return ans;
}
int main()
{
    int n;
    cin >> n;
    int root = 1;
    long long  res=0;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        res+=query(root, 1, inf, a[i] + 1, inf);
        insert(root, 1, inf, a[i]);
    }
    cout<<res<<'\n';
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值