http://acm.hdu.edu.cn/showproblem.php?pid=6058php
題目大意:把一個數組分紅若干子部分,求每部分中第k大的數的和是多少?ios
解題思路:首先可以想到的最暴力的算法是:枚舉數組的每個子部分二重for循環,而後求出每一段的第k大的數又是一重for循環。這樣的想法時間複雜度O(n^3)c++
看一下數據就知道上面的想法確定是不行的。算法
而後可以想到的還有就是對於數組中的數x,咱們只要找到x的左邊有k個比x大的數的位置,而後在右邊找出k個比x大的數就好了。數組
首先暴力去找左邊和右邊的數確定是不行的。咱們能夠經過set容器自動排序這一特性,實現這一想法。咱們知道x的左邊就意味着它的下標是比x的下標要小,同理,x的有邊就意味着它的下標是比x的下標要大,因此咱們只要把x的下標放入set中就能夠了。而後咱們在找 x 的左邊時要求每一次都可以找到下一個比當前x大的值。這樣就須要用數組用來存儲它的左邊和右邊的下一個比他大的位置。最後咱們在左邊統計出k個位置對應着右邊的k個位置,計算他們的位置差的乘積就能夠了。具體內容,請看代碼spa
AC代碼:code
1 #include <iostream> 2 #include <bits/stdc++.h> 3 using namespace std; 4 const int maxn=500005; 5 int vis[maxn],left1[maxn],right1[maxn],nowl,nowr,nextl,nextr,l,r; 6 int main() 7 { 8 int t,n,k; 9 //freopen("1003.in","r",stdin); 10 //freopen("1003.out","w",stdout); 11 scanf("%d",&t); 12 while(t--) 13 { 14 scanf("%d%d",&n,&k); 15 int x; 16 for(int i=1;i<=n;i++) 17 { 18 scanf("%d",&x); 19 vis[x]=i;//處理數據也更方便索引 20 left1[i]=0; 21 right1[i]=n+1; 22 } 23 set<int>s; 24 set<int>::iterator ite; 25 left1[n+1]=0; 26 right1[n+1]=n+1; 27 s.insert(0); 28 s.insert(n+1);//默認第一個位置和最後一個位置上是最大值 29 long long ans=0; 30 for(int i=n;i>0;i--) 31 { 32 s.insert(vis[i]);//由大到小插入x 33 ite=s.find(vis[i]);//x插入的所在位置,它左邊的數都大於x且有ite-1個,對應序列中x左邊大於x的個數 34 ite++; 35 r=*ite;//右邊最靠近x的vis值 36 l=left1[r];//左邊最靠近x的vis值 37 left1[r]=vis[i]; 38 right1[vis[i]]=r; 39 right1[l]=vis[i]; 40 left1[vis[i]]=l;//把x插入l和r之間,這樣很方便索引 41 if(n-i+1<k) 42 continue; 43 nowl=vis[i]; 44 for(int j=0;j<k&&nowl;j++)//左端找出k個比x大的數,有可能左端不足k個 45 nowl=left1[nowl]; 46 nowr=nowl; 47 for(int j=0;j<k&&nowr!=n+1;j++)//從左端位置往右端找,湊足k個數 48 nowr=right1[nowr]; 49 for(int j=0;j<k;j++) 50 { 51 if(nowl==vis[i]||nowr==n+1)//k個數裏面必定要包含x,越過了x就能夠跳出了 52 break; 53 nextl=right1[nowl]; 54 nextr=right1[nowr];//這個位置到下一個位置之間是能夠任意選擇的,因此計算乘積就行了 55 ans+=1ll*(nextl-nowl)*(nextr-nowr)*i; 56 nowl=nextl; 57 nowr=nextr; 58 } 59 } 60 printf("%lld\n",ans); 61 } 62 return 0; 63 }