先是比赛的时候理解错了题意。。第K大惯性思维就是从小数第k个数,样例真是水到爆炸。。原来第k大是从大往小数第K个数,,我记得上次练习主席树和树状数组的时候也为这个纠结了一会,没想到还是没反应过来,其次,比赛的时候其实是想到了正解的,当初没办法验证时间复杂度呀,忘记了链表这玩意了呀 ,所以就GG了,水平还是很菜呀,最近也是被主席树和线段树坑到了,一心想要用主席树上套。。虽然已经知道完全不可能。。
看了一波题解,证明了思路是不会超时的之后,这个题就很好解决 啦,给的k最大只有80,然后数列里的数又是1到n,那么就直接从小到大枚举每一个数,是枚举i,然后可以通过pos[i]确定它在数组中的位置,nex[i]相当于往后的链表,记录该数的下一个数的位置,pre[i]就是往前找的。枚举的时候我们从i的左边找k个比i大的数,(也许没有K个就到了数组的边界,这里要特殊处理),再从右边找K个比i大的数(同理),那么从左边第(K-1)个位置带i这个数组所在的位置pos[i],恰好是满足条件的第一个区间(如果左边不足k个,那么就需要从右边补上K-X个区间,以满足条件),那么从左边到右边恰好满足了条件,再从左边第K-1个数所在的位置到第K个数所在的位置中间的一段空白的数必然是小于i的,那么这些数也可以一个个加入区间并且满足条件,同理左边的那段空白的数也满足条件,最后就是要加一个边界条件,以免去判断的条件,由于所有的数都是在1到N,我们可以将gp[n+1]=n+1,gp[0]=n+2;,这样,在往前往后走的时候,结尾必然是一个大于i的数 ,具体细节看代码:
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#define siz 500005
#define LL long long
using namespace std;
int n,gp[siz],k;
int go_left[siz],go_right[siz];
int pos[siz],pre[siz],nex[siz];
void _Init(){
for(int i=0;i<siz;i++){
go_left[i]=go_right[i]=pos[i]=-1;
pre[i]=nex[i]=-1;
}
}
void solve(){
LL ans=0;
for(int i=1;i<=n;i++){
int len_r=1,j=nex[pos[i]];
go_left[0]=go_right[0]=i;
while(len_r<=k&&j<=n+1){
if(gp[j]>i){
go_right[len_r++] = gp[j];
}
j=nex[j];
}
int len_l=1;
j=pre[pos[i]];
while(len_l<=k&&j>=0){
if(gp[j]>i){
go_left[len_l++] = gp[j];
}
j=pre[j];
}
int le= len_l - 2;
int ri = k - (len_l - 1);
while(le>=0&&ri<len_r-1){
ans += (LL)((LL)(pos[go_left[le]] - pos[go_left[le+1]]) *
(LL)(pos[go_right[ri+1]] - pos[go_right[ri]])) *1LL* i;
--le,++ri;
}
j=pos[i];
int pr = pre[j];
int later = nex[j];
pre[later] = pr;
nex[pr] = later;
}
printf("%lld\n",ans);
}
int main()
{
int T;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d",&gp[i]);
pos[gp[i]]=i;
pre[i]=i-1;
nex[i]=i+1;
}
pre[0]=-1;
nex[n+1]=n+2;
gp[n+1]=n+1,gp[0]=n+2,pos[n+1]=n+1,pos[n+2]=0;
solve();
}
return 0;
}