1. 程式人生 > >題解——動態逆序對

題解——動態逆序對

整數 print main lse 但是 超過 處理 轉化 理解

題目描述:對於給定的一段正整數序列,我們定義它的逆序對的個數為序列中ai>aj且i<j的有序對(i,j)的個數。你需要計算出一個序列的逆序對組數及其刪去其中的某個數的逆序對組數。

CDQ分治

首先由於刪除是很不好處理的,所以我們把刪除改為插入,然後輸出的時候倒著輸出即可

首先這是一個類似於3維偏序的問題:

那麽為了理解我的算法,這裏先放出我對三維偏序的理解。

三維偏序實質上是在所有二維偏序中所有符合條件的數對中進一步找出符合另一個限制條件的數對。

這裏找所有二維偏序中所有符合條件的數對可以用歸並排序(就是相當於歸並排序求逆序對)

那麽樹狀數組求逆序對是個怎麽回事呢?

相當於單獨統計每個數對答案的貢獻,由於樹狀數組的特殊性(前綴和)以及不重不漏的原則,我們只統計在一個數前面出現的,即數對(a,b)中,這個數對對答案的貢獻只統計在b中(下標靠後的那個)

那麽知道這些後我們就可以開始解題了!

將刪除轉為插入後,問題就可以轉化為一個類似於三維偏序的問題:

對於每個數,我們只統計在它之前被插入的數對它造成的貢獻(沒有被刪除的數默認為第一個插入,不然樹狀數組統計不了)

舉個栗子:

我們觀察樣例: 如果是刪除的話,變化過程是這樣的(這裏為了簡化以方便理解,我們暫時不看最後一個刪除)

5 4 2 6 3 1

5 2 6 3 1

2 6 3 1

如果是插入的話,變化過程自然就是反過來

2 6 3 1 ---> ans=4

5 2 6 3 1 ---> ans=7

5 4 2 6 3 1 ---> ans=11

此處註意:由於將刪除變為了插入,因此最先刪除的反而是最後插入的,下面的編號就是插入順序

數字542631
編號(t) 2 3 1 1 1 1
統計到的貢獻 0 1 0 0 1 3

這裏由於我們是分時間輸出,而不是分數字輸出,因此我們將每個數字的貢獻放入它所對應的編號內

所以我們的ans數組:

ans401
下標 1 2 3

又由於編號和輸出順序是相反的,並且觀察插入的特點,逆序對在插入某個數後不可能會變少,只會不變or上升,因此我們的答案應該是對於ans數組的前綴和,並且要倒著輸出這個前綴和

那麽我們將會輸出 4 4 5

嗯?好像有哪裏不對?

為什麽呢?

我們可以發現,我們其實是單獨統計了每個數在插入後的貢獻,

但是我們就像傳統的樹狀數組求逆序對一樣求在一個數前面的數對是不行的

why?

樹狀數組求逆序對對於每個數只求在它前面的數對之所以可行,就是因為在它後面的數對會被在它後面的那個數所統計到

但是在這裏,對於一個被刪除的數來說,它可以統計到別的數,別的數可統計不到它啊

那這麽辦呢?

也許我們可以考慮%&%……##@#¥等一大堆妙妙的算法,

但是!歸並排序並不慢啊!

所以我們考慮暴力一點的解法,

既然只統計前面的,會漏掉後面的,

那麽我們就統計兩次啊!一次統計前面的,一次統計後面的,加在一起不就好了

但是我們會觀察到,只有被刪除的數才會漏掉應該被統計到的貢獻,而沒有被刪除的數是不會的,所以如果我們統計兩次,那麽被刪除的數會統計到正確答案,但是沒有被刪除的數會統計到雙倍貢獻,但是這並沒有什麽影響,在統計完後把沒有被刪除的數的貢獻和/2即可

第一次歸並排序是要篩選出在一個數前面的並且比它大的數(符合基礎逆序對)

第二次歸並排序是要篩選出在一個數後面的並且比它小的數(符合基礎逆序對)

以下是代碼(註意兩次歸並排序由於要篩選出的東西是不同的(大小關系和下標關系頭不同,因此在細節上略有差別):

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define R register int
  4 #define AC 40100
  5 #define lowbit(x) x&(-x)
  6 #define LL long long
  7 #define D printf("line in %d\n",__LINE__);
  8 int n,m,tot=1;
  9 int c[AC];//這。。。不需要離散化?
 10 LL ans[AC];//還是比較妙的?改刪除為插入,這樣就是三維偏序問題,然後統計答案的時候,
 11 //計算時,一個數的貢獻統計入時間靠後的那個數的ans[時間]裏
 12 //每次都加上前一個的時間貢獻(前綴和),這樣就可以分別統計到刪除前的和刪除後的ans。
 13 struct abc{
 14     int t,num;
 15 }s[AC],tmp[AC],s1[AC];
 16 
 17 inline int read()
 18 {
 19     int x=0;char c=getchar();
 20     while(c>9 || c<0) c=getchar();
 21     while(c>=0 && c<=9) x=x*10+c-0,c=getchar();
 22     return x;
 23 }
 24 
 25 void pre()
 26 {
 27     int a;
 28     n=read(),m=read();
 29     tot=m+1;//error!!!因為刪除變成了插入,所以第一個刪除的反而是最後一個插入的
 30     for(R i=1;i<=n;i++) s[i].num=read(),s[i].t=1,s1[i].num=s[i].num,s1[i].t=1;
 31     for(R i=1;i<=m;i++) 
 32     {
 33         a=read();
 34         s[a].t=tot;
 35         s1[a].t=tot--;
 36     }
 37     m++;
 38 }
 39 
 40 inline void add(int x)
 41 {
 42     for(R i=x;i<=m;i+=lowbit(i)) c[i]++;//error!!!這裏的m可能超過n,所以只添加到n是不夠的,可能會導致查詢不到
 43 
 44 }
 45 
 46 inline void cut(int x)
 47 {
 48     for(R i=x;i<=m;i+=lowbit(i)) c[i]--;
 49 }
 50 inline void search(abc x)
 51 {
 52     for(R i=x.t; i ;i-=lowbit(i)) ans[x.t]+=c[i];//直接累加到對應的時間裏
 53 }
 54 
 55 void merge_sort(int l,int r)//歸並,按num排序,用樹狀數組統計合法的t
 56 {
 57     int mid=(l+r) >> 1,t=0;
 58     if(l == r) return ;
 59     if(l < r) 
 60     {
 61         merge_sort(l,mid);
 62         merge_sort(mid+1,r);
 63     }
 64     int i=l,j=mid+1;
 65     while(i<=mid && j<=r)
 66     {
 67         if(s[i].num > s[j].num) 
 68         {
 69             add(s[i].t);//因為num已經符合,所以只要找在它前面的就好了
 70             tmp[++t]=s[i++];
 71         }
 72         else
 73         {
 74             search(s[j]);
 75             tmp[++t]=s[j++];
 76         }
 77     }
 78     while(i<=mid) 
 79     {
 80         add(s[i].t);
 81         tmp[++t]=s[i++];
 82     }
 83     while(j<=r)
 84         search(s[j++]);
 85     for(R i=l;i<=mid;i++) cut(s[i].t);
 86     for(R i=1;i<=t;i++) s[l+i-1]=tmp[i];
 87 }
 88 
 89 void merge_sort_two(int l,int r)//因為上面會少統計,所以這裏再統計一次
 90 {
 91     int mid=(l+r) >> 1,t=0;
 92     if(l == r) return ;
 93     if(l < r) 
 94     {
 95         merge_sort_two(l,mid);
 96         merge_sort_two(mid+1,r);
 97     }
 98     int i=l,j=mid+1;
 99     while(i<=mid && j<=r)
100     {
101         if(s1[j].num < s1[i].num) 
102         {
103             add(s1[j].t);//因為num已經符合,所以只要找在它前面的就好了
104             tmp[++t]=s1[j++];
105         }
106         else
107         {
108             search(s1[i]);
109             tmp[++t]=s1[i++];
110         }
111     }
112     while(j<=r)
113     {
114         add(s1[j].t);
115         tmp[++t]=s1[j++];
116     }
117     while(i<=mid) 
118     {
119         search(s1[i]);
120         tmp[++t]=s1[i++];
121     }
122     for(R i=mid+1;i<=r;i++) cut(s1[i].t);
123     for(R i=1;i<=t;i++) s1[l+i-1]=tmp[i];
124 }
125 
126 void work()
127 {
128     ans[1]/=2;
129     //for(R i=1;i<=m;i++) printf("%lld ",ans[i]);
130     //printf("\n");
131     for(R i=1;i<=m;i++) ans[i]+=ans[i-1];
132     for(R i=m;i>1;i--)   printf("%lld ",ans[i]);
133     printf("%lld",ans[1]);
134 }
135 int main()
136 {
137     freopen("in.in","r",stdin);
138     pre();
139     merge_sort(1,n);
140     merge_sort_two(1,n);
141     work();
142     fclose(stdin);
143     return 0;
144 }

題解——動態逆序對