2017暑假訓練第十五天
既然不用做題了,就細細的理解一下知識點,決定學一點寫一點,寫點有用的東西。
首先是看了一下用樹狀陣列求逆序數,看了好多版本,有直接求算的,有用結構體存取,再排序後計算的,但是總體的思路都是一樣的,i-sum(i),用這個數減去之前輸入的比他小的數的個數,總的看了一下,認為直接算更加方便而且好理解。
思路就是用樹狀陣列存0或1,每輸入一個,更新陣列,並求算比他小的數的個數,ans+=i-sum(i)。最終輸入完就得到了結果。
程式碼如下:
#include<iostream>#include<cstdio>
#include<string.h>
#include<cmath>
using namespace std;
int c[100010];
int n;
int lowbit(int x){
return x&(-x);
}
void add(int x,int y){
for (i=x;i<=n;i+=lowbit(i)){
c[i]+=y;
}
}
int sum(int x){
int sum;
while (x>0){
sum+=c[x];
x-=lowbit(x);
}
return sum;
}
int main(){
int i,j,k,l,ans,x;
while (cin>>n){
memset (c,0,sizeof(c));
ans=0;
for (i=1;i<=n;i++){
cin>>x;
add(x,1);
ans+=i-sum(x);
}
}
}
又看到了一個題目,是逆序數的一種應用,關鍵程式碼段還是:
memset(c,0,sizeof(c));
for(int i=1;i<=n;i++){
S_L[i]=sum(a[i]);
add(a[i],1);
}
所以這也就是樹狀陣列的一大應用,也就是求算某個位置的數左或是右比他大的(或者是比他小的)數的個數,特殊一點也就是逆序數。
這種題型的做法就是用樹狀陣列存0 1,用sum計數比他小的數的個數得以求算。
然後就是這道題:
題意:
給你一個n個整陣列成的序列,每次只能交換相鄰的兩個元素,問你最少要進行多少次交換才能使得整個整數序列上升有序。
這道題乍一看和樹狀陣列毫無關係,藉助數學知識:也就是我們上學期所學的高代的第二章置換當中講到的,每進行一次對換,逆序數減少一,而當逆序數變為0的時候,這個序列就是上升有序的了。這樣一做解釋,這個題目確實不難,就是求這個序列的逆序數。
而這個看完這個題目的部落格,我終於懂了為什麼要用結構體存取的方法以及具體是怎麼用了(這樣寫也是有好處的,自己慢慢學習,發現不足)。
也就是說,如果要求的數極其的大(比如到了10億)這樣的數的大小如果用正常的解法開個10億的陣列就會導致mle,這時候就用到了結構體處理資料,由於數的最大值雖然很大,但是數的數量卻沒有那麼大,這時候,我們把資料進行集中處理,因為1和2的逆序數和1和100000的逆序數是相同的,從而達到處理資料壓縮陣列的效果。
關鍵程式碼如下:(有關於如何求逆序數的程式碼我就沒在寫,還是老的套路,只寫了關鍵的處理資料的程式碼):
#include<iostream>
#include<cstdio>
#include<string.h>
#include<cmath>
using namespace std;
struct point{
int x;//x表示輸入的這個數是多少
int y;//y表示這個數是第幾個被輸入進去的
bool operator <(const point&b) const{
return x<b.x;
}
};
struct point p[500010];
int a[500010];
int n;
int main(){
int i,j,k,l;
cin>>n;
for (i=1;i<=n;i++){
cin>>p[i].x;
p[i].y=i;
}
sort (p+1,p+n+1);
a[p[1].y]=1;
for (i=2;i<=n;i++){
if (p[i],x==p[i-1].x){
b[p[i].y]=b[p[i-1]].y;
}
else {
b[p[i].y]=i;
}
}
}
題意:
二維平面給定n個點(任意兩個點不重合)的座標,然後要你輸出每個點的“等級“。每個點的等級是它的左下放的點個數(包括正下放和正左方的點)。即要你輸出對於每個點(x,y)來說,有多少點的座標(xi, yi)滿足xi<=x且yi<=y。
而關於這道題,我對於部落格上的解釋略蒙,但根據他貼出來的程式碼,大概的意思就是這個題的輸入格式是按照y從小到大來的,所以,“新來”的點一定在之前所有點的上方,那麼,我們只需要對於x建立一個一維的樹狀陣列,每輸入一個數x,就求算sum(x)也就是之前輸入的x比他小的數(也就是他的左下方的數,因為一定是在下面,只要x<新的x,那麼這個點就在新的點的左下方),然後處理add(x,1),反覆處理之後得到結果。之後看的幾個題都是平淡無奇的有關於求逆序數的題,突然看到的一個題讓我很是驚詫,早上的時候還在想,樹狀陣列處理不了區間更新的問題,所以專門學習了一下線段樹相關的更新區間的知識,那個dalao的演算法讓我看的雲裡霧裡,這個部落格的有關樹狀陣列求區間和的解釋便是撥雲見霧。
先看看題目:
HDU 1556 Color the ball(樹狀陣列)
題意: N個氣球排成一排,從左到右依次編號為1,2,3....N.每次給定2個整數a b(a <= b),lele便為騎上他的“小飛鴿"牌電動車從氣球a開始到氣球b依次給每個氣球塗一次顏色。但是N次以後lele已經忘記了第I個氣球已經塗過幾次顏色了,你能幫他算出每個氣球被塗過幾次顏色嗎? 這個題目就是簡單的處理,一個區間的所有元素加1,問最後加了幾次,看起來用線段樹更加省時間的演算法最後竟然不如樹狀陣列的演算法,程式碼如下: #include<iostream>#include<cstdio>
#include<string.h>
#include<cmath>
using namespace std;
int n;
int a[1000010];
int lowbit(int x){
return x&(-x);
}
int usum(int x){
int sum=0;
while (x<=n){
sum+=a[x];
x=x+lowbit(x);
}
}
int dsum(int x){
int sum=0;
while (x>0){
sum+=a[x];
x=x-lowbit(x);
}
}
void uadd(int x,int y){
while (x<=n){
a[x]+=y;
x+=lowbit(x);
}
}
void dadd(int x,int y){
while (x>0){
a[x]+=y;
x-=lowbit(x);
}
}
int main(){
int l,r;
memset (a,0,sizeof(a));
while (cin>>l>>r){
uadd(l,1);
uadd(r+1,-1);
for (int i=l;i<=r;i++){
cout<<dsum(i)<<endl;
}
}//這段程式碼是常用的向上更新向下求和,sum(i)表示第i個數被染色了幾次
memset (a,0,sizeof(a));
while (cin>>l>>r){
dadd(l-1,-1);
dadd(r,1);
for (int i=l;i<=r;i++){
cout<<usum(i)<<endl;
}
}//這段程式碼是反常的向下更新向上求和。
} 程式碼的主體沒變,只有上面兩段的應用方式和之前提到的有所不同。