主席樹初探 & 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<cstdlib>
#include<ctime>
using namespace std;
int f[100005],a[100005];
int main()
{
  freopen("3295.in","w",stdout);
  srand((int)time(0));
  int n=100000,m=50000;
  printf("%d %d\n",n,m);
  for (int i=1;i<=n;i++) a[i]=i;
  int t;
  for (int i=1;i<=1000000;i++) 
  {
    int P=rand()%n+1,Q=rand()%n+1;
    t=a[P];a[P]=a[Q];a[Q]=t;
  }
  for (int i=1;i<=n;i++) printf("%d\n",a[i]);
  f[0]=1;
  for (int i=1;i<=m;i++)
  {
    for (t=0;f[t];t=rand()%n+1);
    f[t]=1;
    printf("%d\n",t);
  } 
  return 0;
}


【代碼】

#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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章