用不同的姿势求逆序对(复习篇)
用不同的姿勢求逆序?qū)?#xff08;復(fù)習(xí)篇)
文章目錄
- 用不同的姿勢求逆序?qū)?#xff08;復(fù)習(xí)篇)
- 前言
- 講解
- 歸并排序
- 樹狀數(shù)組
- 線段樹
- 題目
- 思路
- 代碼
- 歸并排序求逆序?qū)?/li>
- 樹狀數(shù)組求逆序?qū)?/li>
- 線段樹求逆序?qū)?/li>
- 歷屆試題 小朋友排隊解題代碼
前言
最近忙于小項目,感覺很久沒刷題了!
今天在藍(lán)橋上做了一個逆序?qū)Φ念}目(小朋友排隊 ),之前只用過歸并排序求解這類問題。
現(xiàn)在以現(xiàn)在的知識水平,新加了兩種解題姿勢。
目前我比較喜歡以多種姿勢來解一道題,因為可以順帶復(fù)習(xí)一些以前學(xué)過的知識。小聲bb一句:做題不在于多,而在于精!
逆序?qū)?/strong>的概念很簡單。當(dāng)ai > aj,i < j,稱(ai,aj)為一對逆序?qū)Α?/p>
對應(yīng)的還有一個正序?qū)?/strong>,解法幾乎是一致的。 直接問法(裸題):就是給定一個序列,直接讓你求逆序?qū)Α?br /> 隱晦問法:需要根據(jù)特性來推出是求逆序?qū)€數(shù)。如本文提到的小朋友排隊問題。 常見的解法有: 因為我是當(dāng)作復(fù)習(xí),所以本篇博客就粗略講解上面這三種解法。 歸并排序基本做法是,將一個序列不斷二分,直到子序列不能再分了(只有一個元素)就進(jìn)行兩兩合并。在合并過程中 優(yōu)先原則(先取大的還是小的) 可以決定最終序列為升序還是降序。具體實現(xiàn)—》排序?qū)?/p>
歸并排序是如何來求解逆序?qū)?#xff1f; 關(guān)鍵就在于兩個子序列合并過程, 子序列1中的3 > 子序列2中的2, 樹狀數(shù)組主要用于解決區(qū)間修改(一般是單點修改,區(qū)間修改要引入差分),區(qū)間查詢(一般是求區(qū)間和)的問題。 總結(jié)了一大堆基礎(chǔ)知識,那么如何用樹狀數(shù)組來求解逆序?qū)?#xff1f; 樸素做法: 有一個數(shù)字序列,序列中的元素可能重復(fù)。現(xiàn)在要求這個序列的逆序?qū)Α?/p>
比如這樣一個序列(7個元素): __value: 5 4 6 8 9 4 5 index(id) :1 2 3 4 5 6 7 要求逆序?qū)?shù),即求每個元素的前面有多少個比它大的元素。 現(xiàn)在我們來想象一下:把序列元素想象成小球,id是每個小球的唯一編號。現(xiàn)在 在一條路上(一條線段并標(biāo)有數(shù)值)有9個坑位(小球value_max = 9,坑位可以更多,但是沒有必要), 這個例子推小球換成樹狀數(shù)組的單點更新,看后面的坑位幾個有小球 換成樹狀數(shù)組區(qū)間求和即可。 這個是很樸素的做法,所以有比較大的局限性。 當(dāng)序列value值特別大的時候(比如1e9),內(nèi)存可能不夠。 這時需要 將序列的值離散化,換成相對大小即可。 但是因為有value相同的元素,離散化處理起來還是有點小麻煩。 下面介紹一種更簡單的方法: 抓住逆序?qū)κ?統(tǒng)計 在這個數(shù)前面并且比這個樹大 的數(shù) 的數(shù)目 的特性 我們先看如何來處理這個排序好的序列。 現(xiàn)在想象有一條線段,線段上標(biāo)有一系列id數(shù)值。 我們根據(jù)排好的的序列的順序,依次在線段上相應(yīng)位置(線段上的id和元素id一一對應(yīng))插入元素所對應(yīng)的id值(id就是原來序列的位置), 現(xiàn)在來看一下,為什么元素value相同時,id大的優(yōu)先? 因為兩個相同的元素不能構(gòu)成逆序?qū)?#xff0c;如果讓id小的優(yōu)先插入, 那么當(dāng)后面value值相同(id更大)的元素插入,統(tǒng)計時(以當(dāng)前id向前看) 就會多統(tǒng)計到逆序?qū)Φ臄?shù)目,因為把value值相同的也算進(jìn)去了。 細(xì)品一下很容易領(lǐng)悟到。 我覺得這種做法很精妙,不需要離散化處理即可處理很大的數(shù)據(jù)。因為這種做法只跟元素個數(shù)有關(guān)了。但是需要先排序,相對樸素做法會慢一些。 這種做法放到樹狀數(shù)組上就是單點更新,求前綴和。 線段樹也是主要解決區(qū)間修改,區(qū)間查詢的問題。但是相比于樹狀數(shù)組,它在區(qū)間修改方面更方便,線段樹也更強(qiáng)大些,有各種變形。 線段樹做法和樹狀數(shù)組做法差不多。 在上面提到的 樹狀數(shù)組來求逆序?qū)?的簡單做法種 ,數(shù)據(jù)結(jié)構(gòu)換成線段樹, 具體實現(xiàn)請參考本文的代碼~ 用于練習(xí)的題目: 洛谷: P1908 逆序?qū)?--》 這個是裸題,直接寫即可 藍(lán)橋: 試題 歷屆試題 小朋友排隊 --》解題思路: 很明顯只需要統(tǒng)計出每個小朋友的交換次數(shù),然后根據(jù)等比數(shù)列求和求解最終的結(jié)果。 為了統(tǒng)一處理,求解小朋友后面部分的人數(shù)時,把整個序列倒過了求解逆序?qū)Α?br /> 總之,我們要分兩次求逆序?qū)?#xff1a; 確定了是求解逆序?qū)ΑN覀兙涂梢愿鶕?jù)自己的知識儲備, 之前寫過,可以去我的 排序?qū)?考考古,當(dāng)時幾乎只貼了一份稚嫩的代碼。現(xiàn)在回想起來真不應(yīng)該,后面再去認(rèn)真整理… 我今天重新寫了一遍,代碼如下:
以上是生活随笔為你收集整理的用不同的姿势求逆序对(复习篇)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
(其他高級解法目前觸及到了我的知識盲點…)
(樹狀數(shù)組和線段樹解法是類似的,只是換成了不同的數(shù)據(jù)結(jié)構(gòu)。)講解
歸并排序
比如現(xiàn)在歸并過程中(原則:優(yōu)先取大的)有兩個子序列待合并:
歸并臨時存儲數(shù)組 tmp[] = {},逆序?qū)?shù) ans = 0;
子序列1 : 5 3
子序列2: 4 2 1
5 > 3 -----》子序列1中的5 會大于 子序列2 中的4以及它后面的所有元素,
此時可以統(tǒng)計到子序列2和子序列1中的5構(gòu)成的逆序?qū)?shù):
(5,3)(5,2),(5,1),這個對數(shù)就是此時子序列2中的元素個數(shù)。
ans += 3;tmp = {5}.
繼續(xù)合并,子序列1中的3 < 子序列2中的4:
tmp = {5,4}
ans += 2
tmp = {5,4,3}
最后tmp中加上剩下子序列中沒有比較的元素:
tmp = {5,4,3,2,1}
…樹狀數(shù)組
關(guān)于樹狀數(shù)組的入門題目
樹狀數(shù)組的結(jié)構(gòu)(圖片來源B站目前樹狀數(shù)組Top1講解視頻):
t[]數(shù)組用于存儲 對應(yīng)位置上的a[]數(shù)組元素以及在它的部分元素 的和。
基礎(chǔ)性質(zhì):
(ps : 2+lowbit(2) = 4,4+lowbit(4) = 8)
[l,r]區(qū)間和只需要用前綴和sum[r] - sum[l-1]即可得到。
我們需要根據(jù)小球的value值將小球推到對應(yīng)數(shù)值的坑里,而且每次只能推一個小球入坑(一個坑可以放很多個對應(yīng)value值的小球)。現(xiàn)在,每次推入一個小球,就可以看一下,這條路上在即將要推入的坑后面有幾個坑是已經(jīng)有小球的(之前推進(jìn)去了更后面的坑,即值比當(dāng)前大,這樣就構(gòu)成逆序?qū)α?#xff09;。后面有小球的坑數(shù) 就是 可以和這個小球的value構(gòu)成逆序?qū)Φ臄?shù)目。依次做下去就可以統(tǒng)計到這個序列的逆序?qū)倲?shù)目。
所以我們可以先對原序列從大到小排序,這里的排序規(guī)則需要注意一點,因為有相同的元素,所以需要增加一個規(guī)則:當(dāng)元素value值相同時,需要讓元素下標(biāo)(id)大的優(yōu)先排序。這是為什么?
在插入前,我們可以在線段上看一下,在這個id前有多少個已經(jīng)插入的元素。
這些前面的插入的元素一定時比當(dāng)前元素value大的,因為我們從大到小預(yù)先排好了順序。所以這些元素的個數(shù) 就是可以和 當(dāng)前即將插入的id所對應(yīng)的元素 構(gòu)成逆序?qū)Φ臄?shù)目。
初始化t[] = {0},每插入一個元素,t[id] += 1,
統(tǒng)計就是統(tǒng)計id前 1的個數(shù)。
…線段樹
關(guān)于線段樹的入門題目
線段樹結(jié)構(gòu)(圖片來源百度圖片):
線段樹這里不多介紹了。
統(tǒng)計時,把求前綴和換成求[1,id-1]的區(qū)間和即可。題目
思路
關(guān)鍵就是如何統(tǒng)計每個小朋友的交換次數(shù),從題目的只允許相鄰兩個小朋友作交換。第一時間想到排序中的:冒泡排序,歸并排序。根據(jù)題目數(shù)據(jù)范圍和時限,很快可以pass掉冒泡~(幾種排序算法性能的比較)。
確定可以用歸并排序做,再確定如何統(tǒng)計每個小朋友的交換次數(shù)。
我們可以知道,一個小朋友 需要和前面比他大的和后面比它小的人交換位置。
所以一個小朋友的交換次數(shù) = 前面比他大的人數(shù) + 后面比它小的人數(shù)
》很快可以延申到:
采用多種姿勢來解題了~
最下方貼了一份我采用樹狀數(shù)組的求解的本題代碼,其他方法,可以根據(jù)本文中的參考代碼,適當(dāng)修改一下即可。代碼
歸并排序求逆序?qū)?/h3>
樹狀數(shù)組求逆序?qū)?/h3>
/*
樹狀數(shù)組求逆序?qū)?*/
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 5e5+5;
int n;
int t[N];
struct Node
{int v;int id;bool operator<(const Node& x)const{if(v == x.v){return id > x.id;}return v > x.v;}
}node[N];
inline int lowbit(int x)
{return x&-x;
}void add(int x,int v)
{for(int i = x; i <= n; i+=lowbit(i)){t[i] += v;}
}LL get_sum(int x)
{LL sum = 0;for(int i = x; i > 0; i-=lowbit(i)){sum += t[i];}return sum;
}
int main()
{cin>>n;for(int i = 1; i<= n; ++i){cin>>node[i].v;node[i].id = i;}sort(node+1,node+n+1);LL ans = 0;for(int i = 1; i<= n; ++i){ans += get_sum(node[i].id-1);add(node[i].id,1);}cout<<ans<<endl;return 0;
}
線段樹求逆序?qū)?/h3>
/*
線段樹求逆序?qū)?*/
#include <iostream>
#include <cstdio>
#include <algorithm>
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
using namespace std;
const int N = 5e5;
int sum[N<<2];struct Node
{int v;int id;bool operator<(const Node &x)const{if(v == x.v) return id > x.id;return v > x.v;}
} node[N];//向上更新
inline void push_up(int rt)
{sum[rt] = sum[rt<<1] + sum[rt<<1|1];
}// 建樹和向下傳遞這里不需要...//單點更新
void update_point(int pos,int v,int rt,int l,int r)
{if(l == r){sum[rt] += v;return;}int mid = (l + r) >> 1;if(pos <= mid) update_point(pos,v,lson);else update_point(pos,v,rson);push_up(rt);
}
//區(qū)間查詢
int query(int L,int R,int rt,int l,int r)
{if(L > R) return 0;if(L <= l && r <= R){return sum[rt];}int mid = (l + r) >> 1;int res = 0;if(L <= mid)res += query(L,R,lson);if(R > mid)res += query(L,R,rson);return res;
}
int main()
{int n;//freopen("test.in","r",stdin);//freopen("test.out","w",stdout);scanf("%d",&n);for(int i = 1; i <= n; ++i){scanf("%d",&node[i].v);node[i].id = i;}sort(node+1,node+n+1);long long ans = 0;for(int i = 1; i <= n; ++i){ans += query(1,node[i].id-1,1,1,n);update_point(node[i].id,1,1,1,n);}printf("%lld\n",ans);return 0;
}
歷屆試題 小朋友排隊解題代碼
#include <iostream>
#include <cstring>
#include <algorithm>
const int N = 1e5+5;
typedef long long LL;
using namespace std;
struct Node
{int val;int id;int cnt;bool operator<(const Node &x)const{if(val == x.val){return id > x.id;}return val > x.val;}
} a[N];
LL b[N],t[N];
int n;
inline int lowbit(int x)
{return x&(-x);
}
void add(int x,int v)
{for(int i = x; i <= n; i+= lowbit(i)){t[i] += v;}return;
}
LL get_sum(int x)
{LL sum = 0;for(int i = x; i > 0; i-=lowbit(i)){sum += t[i];}return sum;
}
int main()
{cin>>n;for(int i = 1; i <= n; ++i){cin>>a[i].val;a[i].id = i;}sort(a+1,a+n+1);for(int i = 1; i <= n; ++i){b[a[i].id] = get_sum(a[i].id-1); //前面比它大的add(a[i].id,1);}memset(t,0,sizeof(t));for(int i = n,j = 0; i; --i,++j){b[a[i].id] += j - get_sum(a[i].id-1); //后面比它小的add(a[i].id,1);}LL ans = 0;for(int i = 1; i <= n; ++i){ans += (1 + b[i]) * b[i] / 2;}cout<<ans<<endl;return 0;
}
總結(jié)
- 上一篇: python 判断类是否存在某个属性或方
- 下一篇: 矩形法求定积分的原理和实现