【CQOI2011】【BZOJ3295】动态逆序对

Description

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

Input

输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数。以下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

Source

cdq分治过这题好简单啊www

据说主席树会被卡常数cdq完全不担心

用树状数组预处理出初始序列中逆序对个数,包括每一位的数对应的逆序对个数.

然后开一个数组叫new_inv,用来记录已经被删除的数构成的序列的逆序对个数(因为如果直接在ans里减掉每个数的逆序对,有一些逆序对会被重复计算进去)

然后就开始裸cdq分操作就可以了.

顺便其实这是我第一次用树状数组求逆序对.刚学的.仰慕PoPoQQQ神犇>ω<从他那里get了简单的树状数组函数的写法(因为我才刚会用树状数组两三天→_←别问我为什么才刚学会树状数组因为我是沙茶233)

关于树状数组求逆序对:

其实就相当于把那些数逐个插入进树状数组,看看插入进去的时候比它小的数有多少.

具体的可以看这篇文章

//AC code by CreationAugust
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define MAXN 101000
#define lbt(x) (x&-x)//扯淡的算符优先级= =不加()就会bomb
using namespace std;
int n,m;
long long ans;
int tot;
int a[MAXN],b[MAXN];//用于树状数组统计逆序对
int new_inv[MAXN];//表示删除某个元素后,已经删除的元素构成的序列里的逆序对数目
int tim[MAXN];
struct Query
{
    int num,pos;//num为删除的数,pos为num出现的位置,x为操作出现时间
    int x;
    bool operator <(const Query& a)const{
        return pos<a.pos;
    }
}ques[MAXN],newq[MAXN];
int c[MAXN];
int num[MAXN];
void modify(int i,int flag)//这种写法把向下修改和向上修改写到一起
{
    for (;i>0&&i<=n;i+=lbt(i)*flag)
    {
        if (tim[i]!=tot)
            c[i]=0,tim[i]=tot;
        c[i]++;
    }
}
int sum(int i,int flag)//同上,两种查询压一起
{
    int ret=0;
    for (;i>0&&i<=n;i+=lbt(i)*flag)
        if (tim[i]==tot) ret+=c[i];
    return ret;
}
void solve(int l,int r)
{
    int mid=(l+r)>>1,tp1=l,tp2=mid+1;
    if (l==r)
    {
        printf("%lld\n",ans);
        ans-=num[ques[l].pos];
        ans+=new_inv[l];//因为如果单纯删除的话,某个逆序对会被重复删除两次
        //因此要再加上删除它之后操作序列里新出现的逆序对
        return;
    }
    for (int i=l;i<=r;i++)//按时间划分操作
        if (ques[i].x<=mid) newq[tp1++]=ques[i];
        else newq[tp2++]=ques[i];
    memcpy(ques+l,newq+l,sizeof(Query)*(r-l+1));
    solve(l,mid);
    tot++;
    int j=l;
    for (int i=mid+1;i<=r;i++)//更新新出现的逆序对
    {
        for (;j<=mid&&ques[j].pos<ques[i].pos;j++)
            modify(ques[j].num,-1);
        new_inv[ques[i].x]+=sum(ques[i].num,1);
    }
    tot++;
    j=mid;
    for (int i=r;i>=mid+1;i--)
    {
        for (;j>=l&&ques[j].pos>ques[i].pos;j--)
            modify(ques[j].num,1);
        new_inv[ques[i].x]+=sum(ques[i].num,-1);
    }
    solve(mid+1,r);//剩余部分操作仍然按照之前的关键字排序的顺序分治
    tp1=l,tp2=mid+1;
    for (int i=l;i<=r;i++)
        if ((ques[tp1]<ques[tp2]||tp2>r)&&tp1<=mid) newq[i]=ques[tp1++];
        else newq[i]=ques[tp2++];
    memcpy(ques+l,newq+l,sizeof(Query)*(r-l+1));
}
int main()
{
    freopen("inverse.in","r",stdin);
    freopen("inverse.out","w",stdout);
    scanf("%d%d",&n,&m);
    //树状数组求出初始逆序对
    for (int i=1;i<=n;i++) scanf("%d",&a[i]),b[a[i]]=i;
    for (int i=1;i<=n;i++)
    {
        num[i]=sum(a[i],1);
        modify(a[i],-1);
        ans+=num[i];
    }
    ++tot;
    for (int i=n;i>=1;i--)
    {
        num[i]+=sum(a[i],-1);
        modify(a[i],1);
    }
    for (int i=1;i<=m;i++)
    {
        scanf("%d",&ques[i].num);
        ques[i].pos=b[ques[i].num];
        ques[i].x=i;
    }
    sort(ques+1,ques+m+1);//整个操作序列按pos关键字排序
    solve(1,m);
}
时间: 2024-11-03 02:02:41

【CQOI2011】【BZOJ3295】动态逆序对的相关文章

「CQOI2011」动态逆序对

「CQOI2011」动态逆序对 传送门 树套树. 删除一个位置的元素带来的减损数等于他前面大于它的和后面小于它的,然后这个直接树状数组套主席树维护一下就好了. 参考代码: #include <cstdio> #define rg register #define file(x) freopen(x".in", "r", stdin), freopen(x".out", "w", stdout) template &

bzoj3295【CQOI2011】动态逆序对

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

bzoj3295动态逆序对

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

【CQOI2011】动态逆序对 BZOJ3295

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

【BZOJ3295】【CQOI2011】动态逆序对

cdq分治经典例题,然而智商掉线傻逼错误坑了两天 原题: 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数. N<=100000 M<=50000 此题修改和询问绑定完全离线,可以直接倒序变成插入,然后就是三维数星星辣(? ?????)? x为下标,y为值,z为时间轴,x排序,z_cdq分治,y树状数组 不用搞两次cdq分治,每次按x递增查找比y大的

BZOJ 3295 【Cqoi2011】 动态逆序对

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

【BZOJ】【3295】【CQOI2011】动态逆序对

树套树 Orz zyf神犇 时光倒流……逆序处理,将删点改为加点,动态维护序列. 由于是动态,要加点,所以用树状数组:同时又需要求序列中求比当前元素大/小的元素个数,所以要用平衡树. 所以方法就是在树状数组的每个节点上维护一棵这个节点表示的区间的平衡树. 为什么这样做是对的呢?因为求<k的元素个数这类操作满足区间加法,所以可以把多棵平衡树上的结果(一棵平衡树表示一个区间)加起来,就是整个区间的结果. (我一开始想成带修改的区间第K大那种做法了……就是树状数组套权值线段树……sigh) WA:an

BZOJ3295 动态逆序对 树套树, 树状数组套线段树(主席树)

Orz黄学长,蒟蒻在黄学长的带领下,通过阅读黄学长的代码!终于会了这道题! 首先我想先说一下这道题的思路(准确来说是黄学长的). 很明显,树状数组应该不用讲吧!关键是内存怎么开,维护一些什么样的数据? 其实我们通过观察,很快可以发现,你维护被删的数比维护所有的数轻松多了(不管是空间上,还是时间上).所以我们就可以从这方面想!(其实我一开始的思路,因为这道题我已经看过很久了,一直想写,毕竟是白书里面的一道例题嘛!一开始,蒟蒻的我是打算这样的用树状数组套权值线段树,并且是维护所有的数,我发现空间不够

【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行,依次为删除每个元素之前,逆序对的

[CQOI2011]动态逆序对

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