poj 2104 <排序分块,区间第k大>/<第一次用主席树>2个方法

来源:互联网 发布:禁锢网络剧百度云 编辑:程序博客网 时间:2024/06/07 04:05

给一个序列,查找区间第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;}


阅读全文
1 0
原创粉丝点击