题意
给一组数,这一组数包含很多个子序列,求这些子序列第K大元素的和。
题解
观察发现K很小,因此可以从此着手。
首先我们可以观察一下2,3,4,1,5(求第2大的和)这个序列的计算情况
我们可以发现,4出现了三次。因为右边有一个5,如果子序列包含这一个5,然后左边没有比4大的数的时候,4是第2大的数,也就是符合情况的数。这时候,左边有三种情况符合这种条件,即左边没有元素,左边有3,左边有2,3。因此最后结果就是3*1,因此4出现了三次。
根据上述结论,我们就可以针对每个位置的数尝试去向左向右遍历,然后统计符合条件的情况数。需要特别注意的是,由于我们不知道左边有多少个数比这个数大,右边有多少个更大的,因此我们需要枚举,但是不能每次枚举都去扫描,这样会造成超时。正确的做法是,先向左、向右扫描K个比这个数大的数,记录一下位置。然后再去枚举,这时候每次枚举就只需要查询一下数组就可以了,时间复杂度每次是O(1)的。
在扫描的时候我们可以用链表去优化,用链表优化的话,在处理的时候就需要先按数字大小把下标排个序,然后从代表数字小的下标开始扫描,扫描过程中删去比这个数字小的元素(因为这个元素在以后所有的扫描过程中都不会起任何作用)经过这样的优化处理,就可以以1S的时间复杂度通过这道题。
注意事项
数据很水。。
代码
#include<bits/stdc++.h>
#define LL long long
#define UP(i,l,h) for(int i=l;i<h;i++)
#define DOWN(i,h,l) for(int i=h-1;i>=l;i--)
#define W(t) while(t)
#define MEM(a,b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define MAXN 500010
#define COUT(x) cout<<x<<endl
using namespace std;
typedef pair<int,int> Node;
Node nodes[MAXN];
int a[MAXN],nxt[MAXN],bef[MAXN];
int main() {
int t,n,k;
scanf("%d",&t);
W(t--) {
scanf("%d%d",&n,&k);
UP(i,0,n) {
scanf("%d",&a[i]);
nodes[i]=Node(a[i],i),nxt[i]=i+1,bef[i]=i-1;
}
sort(nodes,nodes+n);
LL sum=0;
UP(s,0,n) {
int i=nodes[s].second;
int aa[100],bb[100];
MEM(aa,-1),MEM(bb,-1);
int now=i,num=0;
W(now<n) {
if(a[now]>a[i]) num++;
else if(a[now]<a[i]) {
nxt[bef[now]]=nxt[now];
bef[nxt[now]]=bef[now];
}
if(aa[num]==-1) aa[num]=now;
if(num==k) {
break;
}
now=nxt[now];
}
now=i,num=0;
W(now>=0) {
if(a[now]>a[i]) num++;
else if(a[now]<a[i]) {
nxt[bef[now]]=nxt[now];
bef[nxt[now]]=bef[now];
}
if(bb[num]==-1) bb[num]=now;
if(num==k) {
break;
}
now=bef[now];
}
UP(j,0,k+1){
if(k-1-j<0) continue;
if(aa[j]==-1||bb[(k-1)-j]==-1) continue;
int tmpa,tmpb;
if(aa[j+1]!=-1)tmpa=aa[j+1];
else tmpa=n;
if(bb[(k-1)-j+1]!=-1) tmpb=bb[(k-1)-j+1];
else tmpb=-1;
// COUT(tmpa<<" "<<aa[j]<<" "<<bb[(k-1)-j]<<" "<<tmpb<<" "<<a[i]<<" "<<j );
sum+=(LL)(tmpa-aa[j])*(bb[(k-1)-j]-tmpb)*a[i];
}
}
printf("%I64d\n",sum);
}
}