poj 2104 <排序分塊,區間第k大>/<第一次用主席樹>2個方法+整體二分
給一個序列,查詢區間第k大,用分塊來實現
首先將區間分為每塊block大小,也就有num=n/block塊,if(n%block==0)num++.
然後每次在定義每個塊其左右邊界的時候進行排序,那麼就得到一個每塊內排好序的塊。
查詢的時候因為是查詢區間第k大,那麼我們可以二分出這個區間最大能滿足(區間小於其的數<k),因為區間小於其的數<k,也就是這個數排第k個(這裡可以好好想想為什麼)。
對於一個查詢的區間,如果其包括一個完整的塊就是在這個塊中二分,對於左右邊界就線性查詢。
TLE-1 分塊的大小可以分成sqrt(n*log(n)),這樣查詢的速度會塊許多,但如果n==1,那麼log(1)=0,注意這個點(re,就因為除0和陣列下標越界)
TLE-2 二分的時候,如果是確定某個陣列的值,可以二分這個陣列(排好序)的值,而不用從left1=-1e9,right1=1e9
wa1 排序了q1作為分塊的時候使用,二分的時候就不能用q1,應該另外設定一個數組q3
#include<iostream> #include <algorithm> #include <stdio.h> #include <string.h> #include <math.h> #include <queue> #include <vector> using namespace std; int l[5500]; int r[5500]; int belong[105000]; int q1[105000]; int q2[105000]; int q3[105000]; int n,block,num; void build(){ double f1=n; block=sqrt(f1*log(f1)); if(block==0)block=1; num=n/block;if(n%block!=0)num++; int i; for(i=1;i<=num;i++){ l[i]=(i-1)*block+1; r[i]=min(n,i*block); //每個block個邊界,是包括的 sort(q1+l[i],q1+r[i]+1); //注意這裡的排序,後面是需要+1的 // cout << l[i] <<" "<< r[i] << " 排序了"<<endl; //for(j=l[i];j<=r[i];j++) //cout << j << " " << endl; } for(i=1;i<=n;i++){ belong[i]=(i-1)/block+1; } } int find2(int x,int y,int t){ int i; int res=0,ans=0; if(belong[x]==belong[y]){ for(i=x;i<=y;i++){ if(q2[i]<t)res++; } return res; }else{ for(i=x;i<=r[belong[x]];i++) if(q2[i]<t)res++; for(i=l[belong[y]];i<=y;i++) if(q2[i]<t)res++; //cout << res<< "個了" << endl; int left1,right1,mid1; for(i=belong[x]+1;i<belong[y];i++){ left1=l[i];right1=r[i]; ans=0; //cout << "左邊是" << l[i] << " " << r[i] << endl; while(right1>=left1){ mid1=(right1+left1)/2; // cout << "進來是" << q1[mid1] <<" " <<mid1<< endl; if(q1[mid1]<t){ left1=mid1+1; ans=max(ans,mid1-l[i]+1); }else{ right1=mid1-1; } } //cout << "++了" << ans << endl; res+=ans; } return res; } } int find1(int x,int y,int k){ int left1,right1,mid1; left1=-(1e9+7);right1=1e9+7; //每次都二分n int res=-(1e9+7); while(right1>=left1){ mid1=(right1+left1)/2; // cout << "找" << q1[mid1] <<" " << mid1 << " " <<find2(x,y,q1[mid1])<<endl; if(find2(x,y,mid1)<k){ // cout <<" 符合" << endl; res=max(res,mid1); left1=mid1+1; }else{ right1=mid1-1; } } return res; } int main(){ freopen("in.txt","r",stdin); int i,k,j,f1,f2,f3,f4,t1,t2,t3,t4,l2,m; long long c1,c2,c3,c4; cin >> n>> m; for(i=1;i<=n;i++){ scanf("%d",&q1[i]); q3[i]=q2[i]=q1[i];} sort(q3+1,q3+1+n); build(); for(i=1;i<=m;i++){ scanf("%d %d %d",&t1,&t2,&t3); //cout << find2(t1,t2,5) << endl; printf("%d\n",find1(t1,t2,t3)); } return 0; }
此外,可以這樣進行離散化。
for(i=1;i<=n;i++){
scanf("%d",&q1[i]);
q2[i]=q1[i];
q3.push_back(q1[i]); //用map來進行判斷而離散的速度是非常慢的。
}
sort(q3.begin(),q3.end());
q3.erase(unique(q3.begin(),q3.end()),q3.end());
主席樹,也就是可持久化線段樹。
之所以是可持久化線段樹,是因為其可以儲存其所有歷史變化情況。
也就是說,一個線段樹,每次一個結點的更改(如更改10次),就產生了10棵線段樹(第i棵線段樹與第i-1棵只有log(n)個結點的區別),分別對應每次更改後線段樹的樣子。
如果每次更改都把整棵樹儲存下來是非常浪費空間的,主席樹的作用就是對第i棵樹,進行一次更新後,只重新儲存這次更新的全部結點,那麼這就只是一顆只有一個方向走
到底的樹(只有log(n)個結點),那麼可以用指標進行,把其沒更新的結點用指標指過去。那麼就由改變和未改變的組成了一顆完整的樹啦。
可能這個用指標進行,把其沒更新的結點用指標指過去比較難理解,對著程式碼手動過一遍就好了。
#include <iostream>
#include <stdio.h>
#include <vector>
#include <string.h>
#include <algorithm>
using namespace std;
const int maxn=1e5+7;
int n,m,cnt;
struct ttt{
int l,r,sum; //sum表示這個區間裡面數字的數量
};
int a[maxn];
int root[maxn];
ttt T[maxn*40];
vector<int>v;
int getid(int x){
return lower_bound(v.begin(),v.end(),x)-v.begin()+1; //錯1次,返回下標需要-v.begin()+1
}
void update(int l,int r,int &x,int y,int pos){
T[++cnt]=T[y]; //把T[y]全部給T[cnt],也就是全部給x
T[cnt].sum++;x=cnt; //x表明的是下標
if(l==r)return ;
int mid=(l+r)/2;
if(pos<=mid) update(l,mid,T[x].l,T[y].l,pos);
else update(mid+1,r,T[x].r,T[y].r,pos);
}
int query(int l,int r,int x,int y,int k){//query為查詢,root查的是下標,那麼輸入就為下標
if(l==r)return l;
int mid=(l+r)/2;
int sum=T[T[y].l].sum-T[T[x].l].sum;//差值為在這個期間新增的結點
if(sum>=k)return query(l,mid,T[x].l,T[y].l,k);
else return query(mid+1,r,T[x].r,T[y].r,k-sum); //sum是指這個區間左邊加了多少個值。
}
int main(){
int i,j,k,f1,f2,f3,f4,t1,t2,t3,t4;
//freopen("in.txt","r",stdin);
cin >> n>> m;
for(i=1;i<=n;i++){
scanf("%d",&a[i]);
v.push_back(a[i]);
}
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
for(i=1;i<=n;i++){
update(1,n,root[i],root[i-1],getid(a[i])); //給線段樹加入一個下標為a[i]的數
// cout << i << " !!! " << cnt << endl
}
//v.erase(i),為刪除下標是i的數字。
//v.erase(beg,end),刪除從beg到end區間的資料。
for(i=1;i<=m;i++){
scanf("%d %d %d",&t1,&t2,&t3);
printf("%d\n",v[query(1,n,root[t1-1],root[t2],t3)-1]);
// 得到的下標是數字的下標,但是要在v中輸出就必須-1
}
return 0;
}
之前分塊的方法錯了,這個是正確的,改變的只有find1()函式,二分判斷結果的是下標,然後把這個下標放入值中,而不是二分值。
#include<iostream>
#include <algorithm>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <queue>
#include <vector>
using namespace std;
int l[5500];
int r[5500];
int belong[105000];
int q1[105000];
int q2[105000];
int q3[105000];
int n,block,num;
void build(){
double f1=n;
block=sqrt(f1*log(f1));
if(block==0)block=1;
num=n/block;if(n%block!=0)num++;
int i;
for(i=1;i<=num;i++){
l[i]=(i-1)*block+1;
r[i]=min(n,i*block); //每個block個邊界,是包括的
sort(q1+l[i],q1+r[i]+1); //注意這裡的排序,後面是需要+1的
// cout << l[i] <<" "<< r[i] << " 排序了"<<endl;
//for(j=l[i];j<=r[i];j++)
//cout << j << " " << endl;
}
for(i=1;i<=n;i++){
belong[i]=(i-1)/block+1;
}
}
int find2(int x,int y,int t){
int i;
int res=0,ans=0;
if(belong[x]==belong[y]){
for(i=x;i<=y;i++){
if(q2[i]<t)res++;
}
return res;
}else{
for(i=x;i<=r[belong[x]];i++)
if(q2[i]<t)res++;
for(i=l[belong[y]];i<=y;i++)
if(q2[i]<t)res++;
//cout << res<< "個了" << endl;
int left1,right1,mid1;
for(i=belong[x]+1;i<belong[y];i++){
left1=l[i];right1=r[i];
ans=0;
//cout << "左邊是" << l[i] << " " << r[i] << endl;
while(right1>=left1){
mid1=(right1+left1)/2;
// cout << "進來是" << q1[mid1] <<" " <<mid1<< endl;
if(q1[mid1]<t){
left1=mid1+1;
ans=max(ans,mid1-l[i]+1);
}else{
right1=mid1-1;
}
}
//cout << "++了" << ans << endl;
res+=ans;
}
return res;
}
}
int find1(int x,int y,int k){
int left1,right1,mid1;
left1=1;right1=n; //每次都二分n
int res=-(1e9+7);
while(right1>=left1){
mid1=(right1+left1)/2;
// cout << "找" << q1[mid1] <<" " << mid1 << " " <<find2(x,y,q1[mid1])<<endl;
if(find2(x,y,q3[mid1])<k){
// cout <<" 符合" << endl;
res=max(res,q3[mid1]);
left1=mid1+1;
}else{
right1=mid1-1;
}
}
return res;
}
int main(){
// freopen("in.txt","r",stdin);
int i,k,j,f1,f2,f3,f4,t1,t2,t3,t4,l2,m;
long long c1,c2,c3,c4;
cin >> n>> m;
for(i=1;i<=n;i++){
scanf("%d",&q1[i]);
q3[i]=q2[i]=q1[i];}
sort(q3+1,q3+1+n);
build();
for(i=1;i<=m;i++){
scanf("%d %d %d",&t1,&t2,&t3);
//cout << find2(t1,t2,5) << endl;
printf("%d\n",find1(t1,t2,t3));
}
return 0;
}
整體二分:
思路:把所有值記錄下標id,按結構體排序,然後二分答案,把所有比答案小的值的下標放入樹狀陣列中,這裡有個小技巧。
那就是因為每次區間都是往左走,然後一點點往右邊走。那麼就只有從大區間到左邊小區間的時候,需要這個雙指標往左邊移動,把加過大於此時mid的值再退回來,那麼對於每次查詢summ(q[i].r)-summ(q[i].l-1)就是這個下標區間在樹狀陣列中的個數,也就是值<=mid的個數,如果個數大於等於k,說明結果值必然小於mid,否則大於mid,此時判斷丟到兩邊再進行查詢。
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn=1e5+7;
const int INF=1e9+7;
struct ttt{
int id,key;
};
struct tt1{
int l,r,k,id;
};
int res[maxn],NN;
ttt q1[maxn];
tt1 q2[maxn];
int lowbits(int x){
return x&(-x);
}
int c1[maxn];//注意這裡空間可能要開大一些
int summ(int x){
int sum1=0;
while(x>0){
sum1+=c1[x];
x-=lowbits(x);
}
return sum1;
}
int add(int x,int y){
while(x<=NN){
c1[x]+=y;
x+=lowbits(x);
}
}
int n,m,tot;
tt1 L[maxn],R[maxn];
void solve(int l,int r,int ql,int qr){
if(ql>qr||l>r)return ;
if(l==r){
for(int i=ql;i<=qr;i++){
res[q2[i].id]=l;
}return ;
}
int mid=(l+r)>>1;
int tot1,tot2;tot1=tot2=0;
while(tot<n&&q1[tot+1].key<=mid)tot++,add(q1[tot].id,1);
//因為有序,把所有結果滿足的值放入樹狀陣列中
while(tot>0&&q1[tot].key>mid)add(q1[tot].id,-1),tot--;
int sum1;
for(int i=ql;i<=qr;i++){
sum1=summ(q2[i].r)-summ(q2[i].l-1);
if(sum1>=q2[i].k)L[++tot1]=q2[i]; //個數太多值應該在左邊
else R[++tot2]=q2[i];
}
for(int i=1,j=ql;i<=tot1;i++,j++)q2[j]=L[i];
for(int i=1,j=ql+tot1;i<=tot2;i++,j++)q2[j]=R[i];
solve(l,mid,ql,ql+tot1-1);
solve(mid+1,r,ql+tot1,qr);
}
int cmp1(ttt x,ttt y){
return x.key<y.key;
}
int main(){
int i,j,k,f1,f2,f3,f4,t1,t2,t3,t4,t5;
//freopen("in.txt","r",stdin);
scanf("%d %d",&n,&m);
NN=n;
int min1=2e9,max1=-2e9;
for(i=1;i<=n;i++){
q1[i].id=i;scanf("%d",&q1[i].key);
min1=min(min1,q1[i].key);max1=max(max1,q1[i].key);
}
sort(q1+1,q1+1+n,cmp1);
for(i=1;i<=m;i++){
scanf("%d %d %d",&q2[i].l,&q2[i].r,&q2[i].k);q2[i].id=i;
}
solve(min1,max1,1,m);
for(i=1;i<=m;i++)
printf("%d\n",res[i]);
return 0;
}