解题报告-洛谷P3157 [CQOI2011] 动态逆序对

P3157 [CQOI2011] 动态逆序对

题目描述

对于序列 \(a\),它的逆序对数定义为集合

\[\{(i,j)| i<j \wedge a_i > a_j \} \]

中的元素个数。

现在给出 \(1\sim n\) 的一个排列,按照某种顺序依次删除 \(m\) 个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。

输入格式

第一行包含两个整数 \(n\)\(m\),即初始元素的个数和删除的元素个数。
以下 \(n\) 行,每行包含一个 \(1 \sim n\) 之间的正整数,即初始排列。
接下来 \(m\) 行,每行一个正整数,依次为每次删除的元素。

输出格式

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

输入输出样例 #1

输入 #1

5 4
1
5
3
4
2
5
1
4
2

输出 #1

5
2
2
1

说明/提示

【数据范围】
对于 \(100\%\) 的数据,\(1\le n \le 10^5\)\(1\le m \le 50000\)

【样例解释】
删除每个元素之前的序列依次为:

\[1,5,3,4,2 \]

\[1,3,4,2 \]

\[3,4,2 \]

\[3,2 \]


解题报告

这题很适合用来训练 CDQ 分治和树套树。

树套树的没什么好讲的,树状数组维护主席树,训练代码能力来的。主要讲 CDQ 分治。

首先把删除的操作转为倒着插入的操作,这样每个数就有一个出现时间。

要写 CDQ 分治,要先明确偏序情况,很显然,这题是个三维偏序:出现时间、在数组中的位置、数值大小。对于数组中的每一个数,我们建立结构体 \(node(t,p,x)\),并将原数组的信息转存到 \(node\) 数组 \(Q\) 中。那么,如果 \(Q_i\) 可以对 \(Q_j\) 的产生价值,当且仅当同时满足以下条件:

\[Q_i.t<Q_j.t \\ Q_i.p<Q_j.p \\ Q_i.x>Q_j.x \]

这样就完成了题意的转化。

然后就是 CDQ 分治的标准操作:先对第一维排序,用归并的方法维护第二维,用树状数组维护第三维。

这里用两点需要注意:

\(1\).对每个计算 \(Q_i\) 价值时,既要统计第二维 \(p\) 比它小并且第三维 \(x\) 比它大的,又要统计第二维 \(p\) 比它大并且第三维 \(x\) 比它小的

\(2\).归并时,我们统计的是每个时间点的新产生的价值,所以最后应该进行一次前缀和。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=101100;

#define ckmax(x,y) ( x=max(x,y) )
#define ckmin(x,y) ( x=min(x,y) )

inline int read()
{
	int f=1,x=0; char ch=getchar();
	while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
	while(isdigit(ch))  { x=x*10+ch-'0';    ch=getchar(); }
	return f*x;
}

int n,m;
int A[N];

// ----------------------------------------

#define lowbit(x) ( x&(-x) )
int T[N];

inline void update(int x)
{ for(;x<=n;x+=lowbit(x)) T[x]++; }

inline void Clear(int x)
{ for(;x<=n;x+=lowbit(x)) T[x]=0; }

inline int query(int x)
{
    int sum=0;
    for(;x>0;x-=lowbit(x))
      sum+=T[x];
    return sum;
}

//--------------------------------------

struct ask
{
    int t,p,x;
    // 出现时间,出现的位置,数值大小
}Q[N],tmp[N];
int ans[N];

bool rule_p(ask u,ask v)
{ return u.p<v.p; }

inline void debug()
{
    cout<<endl;
    for(int i=1;i<=n;i++)
      printf("%d %d %d\n",Q[i].t,Q[i].p,Q[i].x);
}

void cdq(int l,int r)
{
    if(l==r) return ;
    int mid=l+r>>1;
    cdq(l,mid),cdq(mid+1,r);

    int i=l,j=mid+1,pos=l;
    for(;j<=r;j++)
    {
        while(i<=mid && Q[i].p<Q[j].p)
        {
            update(Q[i].x);
            tmp[pos++]=Q[i++];
        }
        ans[Q[j].t]+=query(n)-query(Q[j].x);
        tmp[pos++]=Q[j];
    }
    for(int k=l;k<i;k++) Clear(Q[k].x);
    while(i<=mid) tmp[pos++]=Q[i++];
    for(int k=l;k<=r;k++)
      Q[k]=tmp[k];
    // debug();
    for(int k=r;k>=l;k--)
    {
        if(Q[k].t<=mid) update(Q[k].x);
        else ans[Q[k].t]+=query(Q[k].x);
    }
    for(int k=r;k>=l;k--)
      if(Q[k].t<=mid) Clear(Q[k].x);
}


// ----------------------------------------
int D[N];

bool rule_t(ask u,ask v)
{
    if(u.t!=v.t) return u.t<v.t;
    return u.p<v.p;
}

inline void Prepare()
{
    int tim=n;
    for(int i=1;i<=n;i++) D[A[i]]=i;
    for(int i=1;i<=n;i++) Q[i]=(ask){ 0,i,A[i] };
    for(int i=1;i<=m;i++)
    {
        int x=read();
        Q[D[x]].t=tim--;
    }
    for(int i=n;i>0;i--)  if(!Q[i].t) Q[i].t=tim--;
    sort(Q+1,Q+n+1,rule_t);
    // debug();
}


signed main()
{
    n=read(),m=read();
    for(int i=1;i<=n;i++) A[i]=read();
    Prepare(); cdq(1,n);
    for(int i=1;i<=n;i++) ans[i]+=ans[i-1];
    for(int i=n;i>n-m;i--)  printf("%d\n",ans[i]);
    // debug();
	return 0;
}

posted @ 2025-09-12 20:45  南北天球  阅读(1)  评论(0)    收藏  举报