第三场 hdu 6058 Kanade's sum(思维题+set)
http://acm.hdu.edu.cn/showproblem.php?pid=6058
题目大意:把一个数组分成若干子部分,求每部分中第k大的数的和是多少?
解题思路:首先能够想到的最暴力的算法是:枚举数组的每一个子部分二重for循环,然后求出每一段的第k大的数又是一重for循环。这样的想法时间复杂度O(n^3)
看一下数据就知道上面的想法肯定是不行的。
然后能够想到的还有就是对于数组中的数x,我们只要找到x的左边有k个比x大的数的位置,然后在右边找出k个比x大的数就行了。
首先暴力去找左边和右边的数肯定是不行的。我们可以通过set容器自动排序这一特性,实现这一想法。我们知道x的左边就意味着它的下标是比x的下标要小,同理,x的有边就意味着它的下标是比x的下标要大,所以我们只要把x的下标放入set中就可以了。然后我们在找 x 的左边时要求每一次都能够找到下一个比当前x大的值。这样就需要用数组用来存储它的左边和右边的下一个比他大的位置。最后我们在左边统计出k个位置对应着右边的k个位置,计算他们的位置差的乘积就可以了。具体内容,请看代码
AC代码:
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 }