POJ - 2299 Ultra-QuickSort(线段树+离散化/归并排序)
題目鏈接:點擊查看
題目大意:給出n個數字,求使用冒泡排序所需要交換的次數
題目分析:這個題n給到了5e5,如果直接冒泡排序的話,的時間復雜度肯定就TLE了,所以不能直接暴力模擬
我們換個思路,這個題其實需要轉化一下,就是求每個位置上數字的逆序數,然后加和就是所要求的答案了
因為相對較大的數字經過排序后要往右邊移動,而任意位置的一個數字所要移動的距離就是他的逆序數,即需要和每一個他右邊
比他小的數字所交換一次,所以現在問題轉化為了求這串數字的逆序數了
那么該怎么求逆序數呢?我們就要用到線段樹了,我先來大體描述一下該怎么用線段樹求逆序數吧:
我們先要知道求一個位置上的逆序數,有兩種途徑,一種是通過求該位置之前比它本身大的數字的個數,另一種就是通過求該位
置之后比它本身小的數字的個數
我們先假設一個數列為5,1,3,2,4,我們需要求這個數列每一項的逆序數,我們就用第一種方法來求吧,就是求每個位置之前比它
本身大的數字的個數,首先我們先假設下面五個方塊依次代表著沒一個數字,點亮后的方塊代表前面已經遍歷過的數字
□? □? □? □? □
一開始還沒有進行填充,我們現在開始遍歷一邊這個數列:
首先第一個數字是5,我們需要查找比5大的數字,即查找區間(5+1,5),很顯然這個區間是不存在的,因為左端點大于了右端
點,故第一個數的逆序數為0,在遍歷下一個數字之前,需要將第一個數字點亮,即變成這個樣子。
□? □? □? □? ■
然后我們再處理第二個數字,我們需要找到比1大的數字,即查找區間(1+1,5)中有多少個點亮的方塊,很顯然答案是1,然后
更新點亮的狀態,將第一個燈也點亮
■? □? □? □? ■
接下來處理第三個數字,是3,那么我們需要查找區間(3+1,5),答案是1,更新狀態:
■? □? ■? □? ■
然后是處理數字2,我們需要查找區間(2+1,5),答案是2,更新狀態:
■? ■? ■? □? ■
最后只剩一個4了,我們需要查找區間(4+1,5),答案是1,更新狀態:
■? ■? ■? ■? ■
到此為止,我們將上述結果加和,得到的便是這一串數列的逆序數了,即0+1+1+2+1=5
然后稍微總結一下,關于上述的兩個操作:“查找區間”和“點亮狀態”的操作,分別對應著線段樹中的區間查找和點更新,是不是有
點感覺了?
等等等等!這個題到這里還沒完呢,為什么呢?因為這個題的數據范圍,n是5e5,還不算太大,那么每一個位置上的數呢?范圍
竟然是999999999?我們都知道線段樹是根據范圍來開的,如果按照范圍的話,我們需要開至少4e10的數組才能實現上述的方
法,不過很顯然,會爆內存,那我們該怎么處理呢?
這個題涉及到了二維偏序問題,在這個題目中,每個數字的位置代表一個維度,每個數字的數值代表著另一個維度,因為他的數
值涉及到的范圍很廣,所以我們需要將其離散化來處理,即在存儲每一個數字的時候一起存上他原本的位置,然后對于他的數值
排序,然后對于他的位置來求逆序數,這樣就可以將求逆序數的范圍規范到了1~n之間了,剩下的就可以用線段樹來解決了。
?2019.11.29更新:
學習了歸并排序的原理和內部實現后,發現這個題目用歸并排序能以穩定的nlogn的時間復雜度解決,掛一下代碼
上代碼:更多的會在代碼里注釋
線段樹:
#include<iostream> #include<cstdio> #include<string> #include<cstring> #include<algorithm> #include<stack> #include<queue> #include<map> #include<sstream> #include<cmath> using namespace std;typedef long long LL;const int inf=0x3f3f3f3f;const int N=5e5+100;struct Node {int l,r,sum; }tree[N<<2];struct node {int pos,val; }a[N];void build(int k,int l,int r) {tree[k].l=l;tree[k].r=r;tree[k].sum=0;if(l==r){ return;}int mid=(l+r)>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r); }void add(int k,int pos) {if(tree[k].l==tree[k].r){tree[k].sum=1;return;}int mid=(tree[k].l+tree[k].r)>>1;if(mid>=pos)add(k<<1,pos);elseadd(k<<1|1,pos);tree[k].sum=tree[k<<1].sum+tree[k<<1|1].sum; }int query(int k,int l,int r) {if(l>r)return 0;if(tree[k].r<l||tree[k].l>r)return 0;if(tree[k].r<=r&&tree[k].l>=l)return tree[k].sum;return query(k<<1,l,r)+query(k<<1|1,l,r); }bool cmp(node a,node b) {return a.val<b.val; }int main() { // freopen("input.txt","r",stdin);int n;while(scanf("%d",&n)!=EOF&&n){build(1,1,n);for(int i=1;i<=n;i++){scanf("%d",&a[i].val);a[i].pos=i;}sort(a+1,a+1+n,cmp);//離散化LL ans=0;//注意這里記得開long long,因為計算的過程中會爆int,因為這個WA了一發for(int i=1;i<=n;i++){ans+=query(1,a[i].pos+1,n);//我們求每個數字前比他大的數字的個數add(1,a[i].pos);//每次處理完記得“點亮”這個點}cout<<ans<<endl;}return 0; }歸并排序:
#include<iostream> #include<cstdlib> #include<string> #include<cstring> #include<cstdio> #include<algorithm> #include<climits> #include<cmath> #include<cctype> #include<stack> #include<queue> #include<list> #include<vector> #include<set> #include<map> #include<sstream> using namespace std;typedef long long LL;const int inf=0x3f3f3f3f;const int N=5e5+100;int a[N],b[N];LL merge_sort(int l,int r) {if(l==r)//只有一個元素直接返回return 0;int mid=l+r>>1;LL ans=merge_sort(l,mid)+merge_sort(mid+1,r);//分別往左側和右側遞歸int p=l,q=mid+1;for(int i=l;i<=r;i++)//合并,數組b是輔助數組,輔助排序用的{if(q>r||p<=mid&&a[p]<=a[q])b[i]=a[p++];else{b[i]=a[q++];ans+=mid-p+1;//這里,既然q點屬于當前右區間內最小的點了,那么左區間內比他大的點有mid-p+1個}}for(int i=l;i<=r;i++)a[i]=b[i];return ans; }int main() { // freopen("input.txt","r",stdin); // ios::sync_with_stdio(false);int n;while(scanf("%d",&n)!=EOF&&n){for(int i=1;i<=n;i++)scanf("%d",a+i);printf("%lld\n",merge_sort(1,n));}return 0; }?
總結
以上是生活随笔為你收集整理的POJ - 2299 Ultra-QuickSort(线段树+离散化/归并排序)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HDU - 4725 The Short
- 下一篇: (转)离散化:两种离散化方式详解