[bzoj3295][Cqoi2011][动态逆序对] (树套树)

Description

对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数。给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。

Input

输入第一行包含两个整数nm,即初始元素的个数和删除的元素个数。以下n行每行包含一个1到n之间的正整数,即初始排列。以下m行每行一个正整数,依次为每次删除的元素。

Output

输出包含m行,依次为删除每个元素之前,逆序对的个数。

Sample Input

5 4
1
5
3
4
2
5
1
4
2

Sample Output

5
2
2
1

样例解释
(1,5,3,4,2)?(1,3,4,2)?(3,4,2)?(3,2)?(3)。

HINT

N<=100000 M<=50000

Solution

1.动态主席树(树状数组套主席树)

可以很简单地得出每个点的贡献,考虑怎样不重复计算就行了。去重用动态主席树实现

#include<stdio.h>
#include<string.h>
#define LL long long
#define RG register
#define inline __inline__ __attribute__((always_inline))
#define lbt(x) ((x)&(-x))
#define mid ((x>>1)+(y>>1)+(x&y&1))
#define N 100010
namespace BIT{
    int c[N];
    inline void clr(){
        memset(c,0,sizeof(c));
    }
    inline void add(RG int x,RG int n){
        for(;x<=n;x+=lbt(x))
            c[x]++;
    }
    inline int sum(RG int x){
        RG int res=0;
        for(;x;x-=lbt(x))
            res+=c[x];
        return res;
    }
}
LL ans;
int n,m,num[N],pos[N],left[N],right[N];
namespace FST{
    int sum[N<<6],ls[N<<6],rs[N<<6],rt[N],top,f[50],g[50];
    void modify(int &pr,RG int x,RG int y,RG int pos){
        if(!pr)pr=++top;
        sum[pr]++;
        if(x<y)
            pos<=mid?modify(ls[pr],x,mid,pos):
                modify(rs[pr],mid+1,y,pos);
    }
    inline void Back_d(RG int x,RG int y){
        f[0]=g[0]=0;
        for(;x;x-=lbt(x))
            f[++f[0]]=rt[x];
        for(;y;y-=lbt(y))
            g[++g[0]]=rt[y];
    }
    int Query_l(RG int x,RG int y,RG int pos){
        if(x==y)return 0;
        if(pos<=mid){
            RG int tmp=0;
            for(RG int i=1;i<=f[0];i++)
                tmp-=sum[rs[f[i]]],
                f[i]=ls[f[i]];
            for(RG int i=1;i<=g[0];i++)
                tmp+=sum[rs[g[i]]],
                g[i]=ls[g[i]];
            return tmp+Query_l(x,mid,pos);
        }
        for(RG int i=1;i<=f[0];i++)
            f[i]=rs[f[i]];
        for(RG int i=1;i<=g[0];i++)
            g[i]=rs[g[i]];
        return Query_l(mid+1,y,pos);
    }
    int Query_r(RG int x,RG int y,RG int pos){
        if(x==y)return 0;
        if(pos<=mid){
            for(RG int i=1;i<=f[0];i++)
                f[i]=ls[f[i]];
            for(RG int i=1;i<=g[0];i++)
                g[i]=ls[g[i]];
            return Query_r(x,mid,pos);
        }
        RG int tmp=0;
        for(RG int i=1;i<=f[0];i++)
            tmp-=sum[ls[f[i]]],
            f[i]=rs[f[i]];
        for(RG int i=1;i<=g[0];i++)
            tmp+=sum[ls[g[i]]],
            g[i]=rs[g[i]];
        return tmp+Query_r(mid+1,y,pos);
    }
    void main(RG int p){
        ans-=left[p]+right[p];
        Back_d(0,p-1);
        ans+=Query_l(1,n,num[p]);
        Back_d(p,n);
        ans+=Query_r(1,n,num[p]);
        for(RG int i=p;i<=n;i+=lbt(i))
            modify(rt[i],1,n,num[p]);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    BIT::clr();
    for(RG int i=1;i<=n;i++){
        scanf("%d",&num[i]);
        pos[num[i]]=i;
        left[i]=i-1-BIT::sum(num[i]-1);
        ans+=left[i];
        BIT::add(num[i],n);
    }
    BIT::clr();
    for(RG int i=n;i;i--){
        right[i]=BIT::sum(num[i]-1);
        BIT::add(num[i],n);
    }
    while(m--){
        RG int x;
        scanf("%d",&x);
        printf("%lld\n",ans);
        FST::main(pos[x]);
    }
    return 0;
}
时间: 2024-10-06 12:10:04

[bzoj3295][Cqoi2011][动态逆序对] (树套树)的相关文章

[CQOI2011]动态逆序对(主席树,树状数组)

[CQOI2011]动态逆序对(luogu) 题目描述 对于序列 aa,它的逆序对数定义为集合 \{(i,j)| i<j \wedge a_i > a_j \}{(i,j)∣i<j∧ai?>aj?} 中的元素个数. 现在给出 1\sim n1∼n 的一个排列,按照某种顺序依次删除 mm 个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数. 输入格式 第一行包含两个整数 nn 和 mm,即初始元素的个数和删除的元素个数.以下 nn 行,每行包含一个 1 \sim n1∼n

【分块】bzoj3295 [Cqoi2011]动态逆序对

考虑每次删除pos位置一个数x后,所造成的的影响就是,逆序对的个数少了在1~pos-1中大于x的数的个数加上pos+1~n中小于x的数的个数. 那么我们需要的操作就只有查询区间内比某数大(小)的个数. ↑,分块经典操作,每个块里维护一个有序表. 由于有删除,最好每个块用一个vector. 对于原数列怎么办呢?只需要弄一个vis数组,vis[i]表示i位置的数已经删除即可.(要找到v在原数列中的位置的话,在其所在块暴力即可.) 查询时对整块二分,对要删的元素所在块分成两段暴力. O(n*sqrt(

BZOJ3295 CQOI2011 动态逆序对 树状数组套线段树

离线倒着做,每次加入一个节点后新增的逆序对数量就是其左边大于它的数的个数(左边数的总数-左边小于它的数的个数)+右边小于它的数的个数 用树状数组维护求和,对于树状数组中每个节点v所对应的区间线段树维护区间[l,r]中大于v的数的个数. 最后唯一的问题就是指针版线段树MLE-- #include <cstdio> #include <cstring> #include <cstdlib> #include <iostream> #include <alg

[BZOJ3295][Cqoi2011]动态逆序对

试题描述 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数. 输入 输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数.以下n行每行包含一个1到n之间的正整数,即初始排列.以下m行每行一个正整数,依次为每次删除的元素. 输出 输出包含m行,依次为删除每个元素之前,逆序对的个数. 输入示例 5 4 1 5 3 4 2 5 1 4 2 输出示例 5 2

BZOJ3295: [Cqoi2011]动态逆序对 莫队

这个用莫队做会被卡,但是我还是...... 收获在于树状数组维护后缀和以及双维排序...... 莫队的时间复杂度比想象中的要好一些.... 然而我还是被卡了...... #include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<algorithm> #define MAXN 100005 using namespace std; int pos[M

bzoj3295: [Cqoi2011]动态逆序对 三维数点

为了便于考虑,把删除反序变为增加 于是就变成关于权值和位置和时间的三维数点 一波cdq一波树状数组教做人 (神TM需要longlong,80了一发) 1 #include <bits/stdc++.h> 2 #define mid (l+r>>1) 3 #define ll long long 4 using namespace std; 5 ll n,ret,m,tem; 6 ll tr[200001],a[200001],in[200001]; 7 bool ok[200001

[Luogu P3157][CQOI2011]动态逆序对 (树套树)

题面 传送门:[CQOI2011]动态逆序对 Solution 一开始我看到pty巨神写这套题的时候,第一眼还以为是个SB题:这不直接开倒车线段树统计就完成了吗? 然后冷静思考了一分钟,猛然发现单纯的线段树并不能解决这个问题,好像还要在外面再套上一颗树. 这就很shit了.你问我资磁不资磁树套树,我是不资磁的,树套树是暴力数据结构,我能资磁吗? 很不幸,昨天现实狠狠地打了我一脸:时间不够开新坑的,不切题又浑身难受,找了半天题,还是把这道题拉了出来(哈,真香) 不扯淡了,这题还是很显然的. 考虑开

主席树初探 &amp; bzoj 3295: [Cqoi2011] 动态逆序对 题解

[原题] 3295: [Cqoi2011]动态逆序对 Time Limit: 10 Sec  Memory Limit: 128 MB Submit: 778  Solved: 263 [Submit][Status] Description 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数. Input 输入第一行包含两个整数n和m,即初始元素的个数和删除的元

【BZOJ3295】[Cqoi2011]动态逆序对 cdq分治

[BZOJ3295][Cqoi2011]动态逆序对 Description 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数. Input 输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数.以下n行每行包含一个1到n之间的正整数,即初始排列.以下m行每行一个正整数,依次为每次删除的元素. Output 输出包含m行,依次为删除每个元素之前,逆序对的