樹狀數組功能:(適用於:要求不斷求區間和 || 不斷更新區間)
1.快速 求區間和
2.快速 更新區間
具體詳細資料 見大白書 和 輔導資料.
樹狀數組 ---運用了分塊的思想。
用lowbit函數來得到 地址下標.
基本操作:
1.lowbit()函數
原理:利用了負數在計算機中的存儲形式(按位取反+1),你會發現負數與其絕對值在存儲上
最後一個1的位置是相同的 那麼用& 就可以 把一個數字 分爲幾塊。樹狀數組的操作就全基於此!
2.sum()求區間和函數
3.add()更新區間函數
例題:
poj2352
思路 ,因爲星星事先已經按y從小到大排序了,那麼只需要從第一個星星開始 得到它的級數;求級數就要用到區間求和,得到級數後,又要把
該星星加入樹狀數組裏,即更新(是爲了後面星星算級數服務)。
//Accepted 384K 141MS
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAX 32010
int n;
int tree[MAX];
int level[MAX];
int lowbit(int x)
{
return x&(-x);
}
void Add(int x)
{
while(x<MAX)
{
tree[x]+=1;
x+=lowbit(x);
}
}
int Sum(int x)
{
int s=0;
while(x>0)
{
s+=tree[x];
x-=lowbit(x);
}
return s;
}
int main()
{
scanf("%d",&n);
memset(tree,0,sizeof(tree));
memset(level,0,sizeof(level));
for(int i=1;i<=n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
x++;
level[Sum(x)]++;
Add(x);
}
for(int i=0;i<n;i++)
printf("%d\n",level[i]);
return 0;
}
hrbust 1400
和上題大同小異,只是需要注意這裏沒事先排好序,且排序是要把 爲同一起點的車 要按速度從大到小排序!
//Accepted 262ms
#include <iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAX 100002
#define N 1000002
int n;
int c[N];
struct Node
{
int x,v;
bool operator<(const Node& a) const
{
if(a.x==x) return v>a.v;
else
return x<a.x;
}
}car[MAX];
int lowbit(int x)
{
return x&(-x);
}
void Add(int x)
{
while(x<N)
{
c[x]+=1;
x+=lowbit(x);
}
}
int sum(int x)
{
long long s=0;
while(x>0)
{
s+=c[x];
x-=lowbit(x);
}
return s;
}
int solve()
{
memset(c,0,sizeof(c));
int ans=0;
for(int i=1;i<=n;i++)
{
ans+=i-sum(car[i].v)-1;
Add(car[i].v);
}
return ans;
}
int main()
{
while(~scanf("%d",&n))
{
for(int i=1;i<=n;i++)
{
scanf("%d%d",&car[i].x,&car[i].v);
}
sort(car+1,car+1+n);
// cout<<"\n*****************************\n";
// for(int i=1;i<=n;i++)
// printf("%d ",car[i].x);
//cout<<endl;
//for(int i=1;i<=n;i++)
//printf("%d ",car[i].v);
// cout<<"\n*****************************\n";
printf("%lld\n",solve());
}
return 0;
}
hrbust 1161
就需要注意的是:樹狀數組的更新操作 有一個所謂的時間限制!
加一個記錄時間的 數組就OK!
// 560ms
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAX 100002
int n,q,t;
int tim[MAX];
int c[MAX];
int lowbit(int x)
{
return x&(-x);
}
int sum(int x)
{
int s=0;
while(x>0)
{
s+=c[x];
x-=lowbit(x);
}
return s;
}
void add(int x)
{
while(x<MAX)
{
c[x]+=1;
x+=lowbit(x);
}
}
int main()
{
int T;
scanf("%d",&T);
int cas=1;
while(T--)
{
printf("Case %d:\n",cas++);/**注意這個輸出位置*/
memset(c,0,sizeof(c));
int Attack=0;
scanf("%d%d%d",&n,&q,&t);
for(int i=1;i<=n;i++)
tim[i]=-t;/**爲後面鋪墊**/
for(int i=1;i<=q;i++)
{
char s[6];
scanf("%s",s);
if(s[0]=='A')
{
Attack++;
int a;
scanf("%d",&a);
if(tim[a]+t<=Attack)/**判斷能不能抵消攻擊**/
{
tim[a]=Attack;/**能抵消則從這時刻重新計算冷卻時間**/
}
else
add(a);
}
else
{
int fir,last;
scanf("%d%d",&fir,&last);
if(fir>last) swap(fir,last);
printf("%d\n",sum(last)-sum(fir-1));
}
}
}
return 0;
}
poj2085
這一題 其實主要是構造題!不過其中要用到 樹狀數組!
大意:給一個n,和一個m。n爲1~n的序列.m爲多少個 逆序對.
要你求出 由1~n數字組成的 擁有m個逆序對的 字典序最小的排列!
第一步:要求出m<=1+2+3+......+k; 中的k(表示這個逆序對最多由幾個數字組成)--明顯用樹狀數組求 區間和!+二分查找k
第二步:應該按升序輸出1~n-k個數放答案序列的最前面!因爲只需要後面的k序列就可以滿足m個逆序對的條件!
第三步:要求出m=1+2+3+.....+x中的x 這個x一定是介於 k和k-1之間的。不然上一步求出的k就不是最大的。
那麼 計算 k-x;就可以知道實際這個要得到的序列 與 k個數完全降序排列 構成的逆序 少幾個逆序對!
那麼把 n-(k-x) 這個數放到 這個完全降序的逆序列的最前面 就可以 減少k-x個逆序對。就得到了答案。
第四步: 注意輸出和格式就行了!
//xdq 2085 Accepted 524K 110MS C++ 1438B
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAX 50002
#define ll __int64
ll c[MAX];
ll lowbit(ll x)
{
return x&(-x);
}
ll sum(ll x)
{
ll s=0;
while(x>0)
{
s+=c[x];
x-=lowbit(x);
}
return s;
}
void add(ll x)
{
ll val=x;
while(x<MAX)
{
c[x]+=val;
x+=lowbit(x);
}
}
void Init()
{
for(ll i=1;i<=MAX;i++)
add(i);
}
ll find_k(ll key,ll l)
{
ll k,mid;
ll r=1;
while(l>=r)
{
mid=(l-r)/2+r;
if(sum(mid)>=key)
{
k=mid;
l=mid-1;
}
else {r=mid+1;}
}
return k;
}
int main()
{
//freopen("ss.txt","r",stdin);
//freopen("tt.txt","w",stdout);
Init();
ll n;
ll m;
while(~scanf("%I64d%I64d",&n,&m))
{
if(n==-1&&m==-1) break;
ll k,i;
if(m==0)
{
for(i=1;i<n;i++)
printf("%I64d ",i);
printf("%I64d\n",i);
continue;
}
k=find_k(m,n);
for( i=1;i<n-k;i++) /**前n-k個按升序排*/
printf("%I64d ",i);
int x=k-(sum(k)-m);
printf("%I64d ",n-k+x);/**把這個數放在n前面就可以消除多出的 k-x個逆序對**/
for( i=n;i>n-k+x;i--) /**然後就把剩餘的降序排列就ok 了!**/
printf("%I64d ",i);
for( i=n-k+x-1;i>n-k;i--)
printf("%I64d ",i);
printf("%I64d\n",n-k);
}
return 0;
}
如何用樹狀數組求逆序數呢?(當然可以用歸併排序求!)
第一種方法:只要從輸入序列的尾 開始讀數字,讀一個就求它(不包含它)之前的 區間和!
那麼這就是它後面有幾個小於它的數!即逆序對!
第二種方法:當然你從輸入序列頭往後讀 也可以,不過就是反向思維,就是求它之前有幾個比它小的,然後拿當前總數減去 就得到
前面有幾個比它大的,即逆序對!
這個題目因爲 每個數字可能 大到10^9 那麼樹狀數組 下標是開不下的!
所以這裏用到了 離散化 ,把輸入序列的 每個數組之間的 相對大小求出來了!
所有這下 樹狀數組下標就只跟 數據規模 n 有關了!
SGU180
#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 70000
struct Node
{
int data;
int id;
bool operator<(const Node t) const
{
return data<t.data;
}
}a[N];
int b[N];
int n;
long long c[N];
int lowbit(int x)
{
return x&(-x);
}
long long sum(int x)
{
long long s=0;
for(int i=x;i>0;i-=lowbit(i))
s+=c[i];
return s;
}
void add(int x)
{
for(int i=x;i<=n;i+=lowbit(i))
c[i]+=1;
}
int main()
{
while(~scanf("%d",&n))
{
int tot=0,p=-1;
memset(c,0,sizeof(c));
long long ans=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i].data);
a[i].id=i;
}
sort(a+1,a+1+n);
for(int i=1;i<=n;i++)
{
if(p!=a[i].data)/**相同的一定要相對大小相同
不然過不了第二組數據*/
{
tot++;
p=a[i].data;
}
b[a[i].id]=tot;
}
for(int i=n;i>=1;i--)
{
ans+=sum(b[i]-1);/**注意這裏是要求不包含本身的和!**/
add(b[i]);
}
cout<<ans<<endl;
}
return 0;
}
poj1195
二維的樹狀數組。即把分塊的思想 擴展運用!
//xdq 1195 Accepted 4880K 532MS C++ 1036B
/**純裸題。理解二維的lowbit分塊!**/
#include<stdio.h>
#include<string.h>
#define N 1100
int c[N][N],n,arr[N][N];
int lowbit(int x)
{
return x&(-x);
}
void update(int x,int y,int num)
{
int i,j;
for(i=x;i<=n;i+=lowbit(i))
for(j=y;j<=n;j+=lowbit(j))
c[i][j]+=num;
}
int sum(int x,int y)
{
int i,j,s=0;
for(i=x;i>0;i-=lowbit(i))
for(j=y;j>0;j-=lowbit(j))
s+=c[i][j];
return s;
}
int getsum(int x1,int y1,int x2,int y2)
{
return sum(x2,y2)-sum(x1-1,y2)-sum(x2,y1-1)+sum(x1-1,y1-1);
}
int main()
{
int op,x,y,l,b,r,t,a;
while(scanf("%d",&op)!=EOF)
{
if(op==0)
{
scanf("%d",&n);
memset(c,0,sizeof(c));
}
else if(op==1)
{
scanf("%d%d%d",&x,&y,&a);
update(x+1,y+1,a);
}
else if(op==2)
{
scanf("%d%d%d%d",&l,&b,&r,&t);
int ans=getsum(l+1,b+1,r+1,t+1);
printf("%d\n",ans);
}
}
return 0;
}