第一次尝试分桶法,很不顺利,还是太菜了。基本上是借鉴别人的代码,不过还是有很多地方不太懂
///分块思想本质是统计的方法 #include<cstdio> #include<algorithm> #include<vector> #include<iostream> #include<string> #include<cmath> const int N=1e5+5; const int S=1200; using namespace std; #define DEBUG(x) cout << #x << " = " << x << endl int a[N],num[N]; vector<int>vec[N/S]; int main(){ //freopen("in.txt","r",stdin); int n,q; scanf("%d%d",&n,&q); //DEBUG(npb); for(int i=1; i<=n; i++) { scanf("%d",&a[i]); num[i]=a[i]; vec[i/S].push_back(a[i]); } sort(num+1,num+1+n); for(int i=0; i<(n-1)/S; i++) { sort(vec[i].begin(),vec[i].end()); } while(q--){ int L,R,K; scanf("%d%d%d",&L,&R,&K); int l=1,r=n,pos=0; while(l<=r){ int mid=(l+r)/2; int x=num[mid],tl=L,tr=R,cnt=0; ///统计的过程 while(tl<=tr&&tl/S==L/S){ if(a[tl++]<=x)cnt++; } while(tl<=tr&&tr/S==R/S){ if(a[tr--]<=x)cnt++; } while(tl<=tr){ int bn=tl/S; cnt+=upper_bound(vec[bn].begin(),vec[bn].end(),x)-vec[bn].begin(); tl+=S; } if(cnt>=K){ pos=mid,r=mid-1; } else l=mid+1; } printf("%d ",num[pos]); } }
Q1:S的取值为什么是1200?我尝试了400,是错误的。刚开始我的想法是n开根号= =
A1:在做POJ3264时候,发现这个问题的原因在于下标加一减一的问题。我对数组的编号是从1到n编号。假设每块有S个数,第一块的下标就是从1到S,那么第S个数就被错误的分入第1块,原本应该属于第0块。至于为什么增大S的值ac了,可能是因为增大了每块的容量,跨块的可能性降低了。
Q2:外层二分查找什么意思?
A2:外层的二分有点难理解。题意是[l,r]区间的第K大的数。将全局数组排序后,二分查找这个数,这个数的特征是第k大,所以需要统计在目标区间上比当前数字小的个数,根据这个决定查找左区间还是右区间。
参考资料
https://sea96.github.io/2017/05/21/%E5%B9%B3%E6%96%B9%E5%88%86%E5%89%B2/