標籤:style 直接 分享 hdu syn log 技術 逆序 pre
17年的時候在HDU新生賽的時候遇到這樣一道題目, 當時對於這種題目, 只會n^2去數左邊比他大的個數 再相加一下 就是答案了。
無奈n是1e5 毫無疑問的T了。 後來學長說這個不就是歸併排序嗎, 你去學一下歸併就可以做了, 然後我去學了歸併, 又交了一發,
結果竟然還是T(這Y的不是耍我玩嗎)。 然後從另一位學長哪裡聽說了用線段樹去求逆序對, 把n^2變成nlogn就不會T了,最後,
我又學了線段樹,終於這回AC了。寫這個文章的時候,我順便去HDU找了找這道題目,結果找不到這道題目,竟然沒掛出來。。。。
慶幸的是當時比較勤奮,打完新生賽沒幾天就補上了,不然就沒機會做這道題目了。
好了 廢話不多說 我們先上一個n^2的數數演算法。
1 int main() 2 { 3 int n; 4 while(cin >> n) 5 { 6 ans = 0; 7 for(int i = 1; i <= n; i++) 8 { 9 cin >> a[i];10 if(i%2==0 && a[i-1]>a[i])11 {12 swap(a[i-1],a[i]);13 ans++;14 }15 }16 for(int i = 1; i <= n; i+=2)17 {18 for(int j = 1; j < i; j+=2)19 {20 if(a[j] > a[i]) ans++;21 }22 }23 cout << ans << endl;24 }25 return 0;26 }
n^2演算法就是數一下前面有多少個數比現在這個數大 這樣全部跑完只後就是逆序數了。
其中重點是 前面有多少個數比現在這個數大
但是每次從1for一遍到i的位置太浪費時間了
所以我們用線段樹來最佳化這個數數過程
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define lson l,m,rt<<1 4 #define rson m+1,r,rt<<1|1 5 #define ll long long 6 const int N=100005; 7 int sum[N<<2], a[N]; 8 ll ans; 9 void Update(int c, int l, int r,int rt)10 {11 if(l == r)12 {13 sum[rt]++;14 return;15 }16 int m = l+r >> 1;17 if(c <= m) Update(c,lson);18 else Update(c,rson);19 sum[rt]=sum[rt<<1]+sum[rt<<1|1];20 }21 ll Query(int L, int R, int l, int r, int rt)22 {23 if(L <= l && r <= R)24 return sum[rt];25 int m = l+r >> 1;26 ll cnt = 0;27 if(L <= m) cnt+=Query(L,R,lson);28 if(m < R) cnt += Query(L,R,rson);29 return cnt;30 }31 int main()32 {33 ios::sync_with_stdio(false);34 cin.tie(0);35 int n;36 while(cin >> n)37 {38 ans = 0;39 memset(sum, 0, sizeof(sum));40 for(int i = 1; i <= n; i++)41 {42 cin >> a[i];43 if(i%2==0 && a[i-1]>a[i])44 {45 swap(a[i-1],a[i]);46 ans++;47 }48 }49 for(int i = 1; i <= n; i+=2)50 {51 ans+=Query(a[i],n,1,n,1);52 Update(a[i],1,n,1);53 }54 cout <<ans << endl;55 }56 return 0;57 }
線段樹演算法的精髓就是將出現過的數對應的位置標記一下(+1)
假設 i=k時, 查詢一下區間 [a[k], n] 的區間和, 這個和就是(j < k && a[j] > a[k]) 的數目
然後在a[k] 的位置 +1
重複這個過程就能求出解了
是不是很疑惑為什嗎?
當查詢區間的時候, 如果在後面的區間內查詢到次數不為0時, 說明有幾個比他大數在他前面出現過,
線段樹寫法的精髓就是標記位置 進行查詢, 這就是線段樹的寫法。
當然還有樹狀數組的寫法
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=100005; 4 int sum[N], a[N]; 5 int n; 6 int lowbit(int x) 7 { 8 return x&(-x); 9 }10 void Update(int x)11 {12 while(x <= n)13 {14 sum[x]++;15 x += lowbit(x);16 }17 }18 int Query(int x)19 {20 int ans = 0;21 while(x > 0)22 {23 ans += sum[x];24 x -= lowbit(x);25 }26 return ans;27 }28 int main()29 {30 ios::sync_with_stdio(false);31 cin.tie(0);32 while(cin >> n)33 {34 long long ans = 0;35 memset(sum, 0, sizeof(sum));36 for(int i = 1; i <= n; i++)37 {38 cin >> a[i];39 if(i%2==0 && a[i-1]>a[i])40 {41 swap(a[i-1],a[i]);42 ans++;43 }44 }45 for(int i = 1; i <= n; i+=2)46 {47 ans += Query(n) - Query(a[i]);48 Update(a[i]);49 }50 cout << ans << endl;51 }52 return 0;53 }
注意的是 線段樹與樹狀數組求逆序對的時候 數值不能太大 比如a[i] <= 1e9的時候 就不能直接用樹狀數組和逆序數去求了,因為開不了那麼大的空間。
但在這個時候 如果n不是很大 可以先對資料進行離散化 進行樹狀數組或者線段樹處理資料。
PS: 本篇部落格到這裡就結束了,謝謝你的觀看, 如果有疑惑可以在下方留言或者加我QQ:1073223357進行詢問。
同時歡迎各路大牛對我代碼中的漏洞進行指正。
逆序對 線段樹&樹狀數組