主席树初探 & 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

输入第一行包含两个整数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

【题外话】一直想A了这道题。真累啊!开始的想法是——倒着做,一个一个添加,套一个树状数组,然后每次用平衡树去寻找。显然,现在我只会SPLAY,而且代码长,效率渣。今天和SYF大神一起看hzwer大神的博客,加之哲大爷的一语道破,总算会了树状数组套主席树的方法。

【主要思路】首先用树状数组/归并排序/归并树(时代的眼泪)求出总的序列中的逆序对个数。每次删掉的时候,用树状数组维护位置(1--x-1或x+1--n),并且用权值线段树维护权值信息。

【详细算法】

①在之前的计算总的逆序对个数的时候,我们可以顺带的算出front[i]和back[i],表示第i个点之前有front[i]个数比他大,之后有back[i]个数比他小。

②起初刚开始做的时候,主席树内部是空的。因此我们要转化——我们本来计算的是删掉某个数后,将会减少所剩的数列中的多少的逆序对(即对所剩的序列产生多少的贡献);现在我们改成:删掉某个数后,将会对主席树中已经插入的一些节点产生多少的贡献。这样算出来后,用front或back减一下即可。

举个例子。比如有数列5,4,3,2,1,已经删掉了4和1,ans值是3。现在我们要删掉3了。在主席树中,已添加的节点是5和2。通过计算,5,3,2会在3的前面产生一个逆序对,在3的后面产生一个逆序对。而front[3]=2,back[3]=2,那么我们可以知道,ans需要减去front[3]-1与back[3]-1,即最后ans只剩下了1。

③后来套的树状数组具体是怎么实现的呢?比如我们要计算或更新l~r范围内主席树中的某个或某些权值的个数。我们可以转化为计算或更新1--l-1与1--r的前缀,再相减即可。而前缀是可以用树状数组解决的。

【代码】

#include<cstdio>
#include<cstring>
#define N 100005
#define L(x) (x&-x)
using namespace std;
typedef long long LL;LL ans;
struct arr{int l,r;LL sum;}a[6000000];
int front[N],back[N],c[N],root[N],pos[N],data[N],A[31],B[31];
int n,node,del,x,m,i;
inline int ask(int x){int s=0;for (;x;x-=L(x)) s+=c[x];return s;}
inline void up(int x){for (;x<=n;x+=L(x)) c[x]++;}
inline LL ask_more(int l,int r,int num)
{
  if (l>r) return 0;l--;A[0]=B[0]=0;
  for (int i=l;i;i-=L(i)) A[++A[0]]=root[i];
  for (int i=r;i;i-=L(i)) B[++B[0]]=root[i];
  l=1;r=n;LL sum=0;
  while (l!=r)
  {
    int mid=(l+r)>>1;
    if (num<=mid)
    {
      for (int i=1;i<=B[0];i++) sum+=a[a[B[i]].r].sum;
      for (int i=1;i<=A[0];i++) sum-=a[a[A[i]].r].sum;
      for (int i=1;i<=B[0];i++) B[i]=a[B[i]].l;
      for (int i=1;i<=A[0];i++) A[i]=a[A[i]].l;
      r=mid;
    }
    else
    {
      for (int i=1;i<=B[0];i++) B[i]=a[B[i]].r;
      for (int i=1;i<=A[0];i++) A[i]=a[A[i]].r;
      l=mid+1;
    }
  }
  return sum;
}
inline LL ask_less(int l,int r,int num)
{
  if (l>r) return 0;l--;A[0]=B[0]=0;
  for (int i=l;i;i-=L(i)) A[++A[0]]=root[i];
  for (int i=r;i;i-=L(i)) B[++B[0]]=root[i];
  l=1;r=n;LL sum=0;
  while (l!=r)
  {
    int mid=(l+r)>>1;
    if (num>mid)
    {
      for (int i=1;i<=B[0];i++) sum+=a[a[B[i]].l].sum;
      for (int i=1;i<=A[0];i++) sum-=a[a[A[i]].l].sum;
      for (int i=1;i<=B[0];i++) B[i]=a[B[i]].r;
      for (int i=1;i<=A[0];i++) A[i]=a[A[i]].r;
      l=mid+1;
    }
    else
    {
      for (int i=1;i<=B[0];i++) B[i]=a[B[i]].l;
      for (int i=1;i<=A[0];i++) A[i]=a[A[i]].l;
      r=mid;
    }
  }
  return sum;
}
inline void update(int &k,int l,int r)
{
  if (!k) k=++node;a[k].sum++;
  if (l==r) return;
  int mid=(l+r)>>1;
  if (del<=mid) update(a[k].l,l,mid);
  else update(a[k].r,mid+1,r);
}
inline int Read()
{
  char ch=getchar();for (;ch<'0'||ch>'9';ch=getchar());
  int x=0;for (;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
  return x;
}
int main()
{
  n=Read();m=Read();
  for (i=1;i<=n;i++)
  {
    data[i]=Read();pos[data[i]]=i;
    ans+=(front[i]=i-1-ask(data[i]));up(data[i]);
  }
  memset(c,0,sizeof(c));
  for (i=n;i;i--)
    back[i]=ask(data[i]-1),up(data[i]);
  for (i=1;i<m;i++)
  {
    printf("%lld\n",ans);
    del=Read();x=pos[del];
    ans-=front[x]-ask_more(1,x-1,del);
    ans-=back[x]-ask_less(x+1,n,del);
    for (;x<=n;x+=L(x)) update(root[x],1,n);
  }
  printf("%lld\n",ans);
  return 0;
}

主席树初探 & bzoj 3295: [Cqoi2011] 动态逆序对 题解,布布扣,bubuko.com

时间: 2024-08-05 11:10:59

主席树初探 & bzoj 3295: [Cqoi2011] 动态逆序对 题解的相关文章

COGS 1715 &amp; bzoj 3295 [CQOI2011]动态逆序对 题解

(又是一道树套树……自己真是玩疯了……) (题意略) 从网上也看过题解,好像解法很多……比如CDQ+树状数组,树状数组套主席树,树状数组套平衡树……我用的是树状数组套splay. (我会说是因为我不会写CDQ和树状数组套主席树么= =) (不得不吐槽,为啥splay这么快= =) 也没啥可说的,我写的是在线算法,只要在删除一个元素之前统计它前面比它大的数和后面比它小的数的个数(区间求和用树状数组,统计比它小/大的数的个数用平衡树写),把答案减掉对应数值即可. 鉴于这题卡常,我就加了快读和各种in

bzoj 3295: [Cqoi2011]动态逆序对(树套树 or CDQ分治)

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

BZOJ 3295 [Cqoi2011]动态逆序对 树状数组套线段树

题意:链接 方法:树状数组套线段树 解析: 这题基本上写的都是什么CDQ点分治,主席树之类的,然而这我都并不会,所以写了一发平衡树套线段树想卡时卡过去,然而我并没有得逞,T的不要不要的,这里用平衡树套线段树的方法参见我的题解:排队.这道题比那道更要简单. 然后我就打算弃坑了~不过看140142做这道题做的热火朝天的,还是打算回来做一下,yy下树状数组套线段树,然后去看hz的题解,只看懂他写理论部分了,代码部分不知所云,所以还是还是得yy.引用理论部分. 删除某个数,只要统计它之前还存在的比它大的

【刷题】BZOJ 3295 [Cqoi2011]动态逆序对

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

bzoj 3295: [Cqoi2011]动态逆序对

2016-06-22 这个题本想昨天晚上做来,但昨晚狂风大作,暴雨倾盆(听说我们学校最落后的一堵墙都被吹到了),停电了,我只能无聊的瞭望了教学楼一晚上...... 这个题把删除看成插入的话,插入一个点 新增逆序对就是比他早插入的,位置靠前,数比他大或 位置靠后,数比他小.那这就是个三维偏序集,可以用CDQ搞搞了. 这个题也能用树套树,树状数组归并排序做(以后有时间要写一写,恐怕是没可能了%>_<%). 1 #include<cstdio> 2 #include<iostrea

BZOJ 3295: [Cqoi2011]动态逆序对 cdq分治

https://www.lydsy.com/JudgeOnline/problem.php?id=3295 这个妹妹我曾见过的~~~ 之前应该在校内oj写了,似乎还写过题解?发现没写博客就重新水一遍代码水一篇博客好了. 把找逆序对的过程想成一个一个往里塞数字然后找每个数字可以组成的逆序对,把最后要去掉的几个也想成往里塞,所以加一个时间维度用cdq求三维偏序. 1 #include<iostream> 2 #include<cstdio> 3 #include<algorith

【BZOJ 3295】 [Cqoi2011]动态逆序对

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

[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

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

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