第三场 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 }

 

posted @ 2017-08-02 13:58  Wally的博客  阅读(220)  评论(0编辑  收藏  举报