Problem Description
Give you an array A[1..n]of length n.
Let f(l,r,k) be the k-th largest element of A[l..r].
Specially , f(l,r,k)=0 if r−l+1
Input
There is only one integer T on first line.
For each test case,there are only two integers n,k on first line,and the second line consists of n integers which means the array A[1..n]
Output
For each test case,output an integer, which means the answer.
Sample Input
1
5 2
1 2 3 4 5
Sample Output
30
Source
[2017 Multi-University Training Contest - Team 3]
(http://acm.hdu.edu.cn/search.php?field=problem&key=2017%20Multi-University%20Training%20Contest%20-%20Team%203&source=1&searchmode=source)
题意:
在长度为n、元素为1-n的数组中,找出每一个连续子区间内的第k大数,求出这些数的和。
想法:
1-n这些数是已知且少的,而区间个数是众多的,所以我们可以遍历数组中的每个数,求出每个数作为第k大数所在的区间个数, ∑ 区间个数×此时区间的第k大数就是最终的结果。
我们记当前遍历的数为num, pos[i]表示num所在的位置。
1.记录数组中每个数所在的位置,用数组构建一个双向链表。
2.从1作为num开始num++遍历,我们会发现num两边的数一定是比num大的。
我们在num的位置之前和之后分别找k个比它大的数(要找到第k个 因为num本身占一个位置)
3.找到一个num后,将其删掉,并且发现由于连续,位置的差值就是区间的个数。我们删掉的是数,这个位置依然存在。
举个栗子理解一下:
6 3
3 2 1 5 4 6
pos[3]=1 pos[2]=2 pos[1]=3 pos[5]=4 pos[4]=5 pos[6]=6
首先找到pos[1] 两边 2 4 比它大,然后我们删掉这个1
之后我们再找num=2,pos[2],两边是3 4 比它大,我们删掉这个2
以此类推 我们遍历完能成为第k大数的num
下面我们来寻找使4成为第3大数的可能(此时1 2 3已被删去)
其可能性有 左2右0 左1右1 左0右2 三种
在4之前比它大的数有 5 s[1]=5 s[2]=4(s[1]和t[1]存的是num自身的位置)
在4之后比它大的数有 6 t[1]=5 t[2]=6(s[2]和t[2]分别表示num之前和之后第一个大于它的数的位置)
都只有1个 所以只符合第二种可能
s[1]-s[2]+1=2 代表在4之前取左1右1 只有两种可能 即 1 5 4和5 4
t[2]-t[1]=1 代表在4之后取左1右1 只有一种可能 即4 6
最终4成为第3大数的区间只有1*2种
5 4 6
1 5 4 6
tips:
1.注意1LL的用法:为了在计算时,把int类型的变量转化为long long,然后再赋值给long long类型的变量。(在这里wa到心态爆炸QAQ)
2.题意即使懂了,实现起来因为牵扯到位置所以上下限很容易出问题,遍历的各个范围要很懂,不懂就手动模拟试一试XD
#include<cstdio>
using namespace std;
const int maxn=500010;
int pre[maxn],next[maxn],a[maxn],pos[maxn];
int s[maxn],t[maxn];//s[i],t[i]表示位置i上的数
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int n,k;
scanf("%d%d",&n,&k);
for(int i=1; i<=n; ++i)
{
scanf("%d",&a[i]);
pos[a[i]]=i;//当前位置
pre[i]=i-1;//前驱
next[i]=i+1;//后继
}
long long ans=0;
for(int num=1; num<=n-k+1; ++num)//n-k+1? //枚举可能成为第k大的数(比如一个数列中最大的数不可能在任何子数列里成为第二大)
{
int p=pos[num];//当前查找的数的位置
int ss=1,tt=1;//表示左边第ss个大的 右边第tt个大的 但ss[1]和tt[1]表示当前数本身的位置
/*从当前数的位置向两边查找,各找k-1个比当前数大的使得当前数成为第k大*/
for(int i=p; i&&ss<=k+1; i=pre[i])//
s[ss++]=i;
for(int j=p; j<=n&&tt<=k+1; j=next[j])
t[tt++]=j;
s[ss]=0;//在s末加上0表示结束
t[tt]=n+1;//在t末加n+1表示结束
/*任意从当前数之前拿scnt个.0从后拿tcnt个使得当前数成为第k大数*/
for(int scnt=1; scnt<=ss-1; scnt++)//当前数之前比当前大的数里不包括这个数本身 而ss[1]表示当前数 所以要减去这个数
{//scnt代表拿当前数之前比它大的scnt-1个数
int tcnt=k-(scnt-1);//真正在之前有scnt-1个比当前数大的数
if(tcnt<=tt-1&&tcnt>0)//tt-1与上面ss-1同理
ans+=(s[scnt]-s[scnt+1])*1LL*(t[tcnt+1]-t[tcnt])*num;//在当前数之前多余的数其实是第scnt个数 在之后多余的是第tcnt个数
/*当前数遍历完之后删除此节点的数 但位置还存在 因为此数如果小并不影响第k大数*/
if(pre[p]>0)
next[pre[p]]=next[p];
if(next[p]<=n)
pre[next[p]]=pre[p];
pre[p]=next[p]=0;
}
}
printf("%I64d\n",ans);
}
return 0;
}
ps:反正各个临界值一定要注意!