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<k .
Give you k , you need to calculate ∑nl=1∑nr=lf(l,r,k)
There are T test cases.
1≤T≤10
k≤min(n,80)
A[1..n] is a permutation of [1..n]
∑n≤5∗105
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<k .
Give you k , you need to calculate ∑nl=1∑nr=lf(l,r,k)
There are T test cases.
1≤T≤10
k≤min(n,80)
A[1..n] is a permutation of [1..n]
∑n≤5∗105
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]
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
题意:
给你一个关于1~n的排列,让你求这个排列的所有子序列中第k大的数的和
思路:
最简单的暴力枚举区间,时间复杂度为o(n^3*logn)肯定是不可取的
还有一种暴力的想法是枚举每个数,然后找他在那些区间中是第k大的数(对最后结果有贡献),直接敲了了个o(n^2*80)的算法,肯定也是tle,在这个算法中主要是找前边比他大的k个数和后边k个比他大的数的时候耗时(o(n)的算法),但是我们发现我们只是找比他大的数的下表就能确定他所在区间的个数,那么我们只需要维护一个链表就可以了,这样的话可以瞬间将o(n)的算法变为o(k)(毕竟k最大也就80)
介绍几个数组
pre【i】数组:存的是i之前比他大的数的下标(维护的链表中的前一个元素)
np【i】数组:存的是i之后比他大的数的下标(维护的链表中的后一个元素)
s【i】数组:存的是之前第i个比他大的数的下标
z【i】:存的是之后第i个比他大的数的下标
我们从小到大枚举1----n-k+1之间的数,当我们计算完1个时需要把他从链表中删除,这样就可以保证每次枚举一个数时,他的左右全是比他大的数(比他小的数其实存在下标差中,并不是真的删掉,只是我们找的时候不需要找比他小的数)
举个例子:
2 1 3 5 4
下标: 1 2 3 4 5
枚举1的时候,1两侧全是比他大的数,然后把1删除变为
2 3 5 4
下标: 1 3 4 5
我们在枚举2时,2两侧还是只有比他大的数,但是当我们计算个数的时候进行下标运算,被删掉的1也会别计算在里边的
还有一种暴力的想法是枚举每个数,然后找他在那些区间中是第k大的数(对最后结果有贡献),直接敲了了个o(n^2*80)的算法,肯定也是tle,在这个算法中主要是找前边比他大的k个数和后边k个比他大的数的时候耗时(o(n)的算法),但是我们发现我们只是找比他大的数的下表就能确定他所在区间的个数,那么我们只需要维护一个链表就可以了,这样的话可以瞬间将o(n)的算法变为o(k)(毕竟k最大也就80)
介绍几个数组
pre【i】数组:存的是i之前比他大的数的下标(维护的链表中的前一个元素)
np【i】数组:存的是i之后比他大的数的下标(维护的链表中的后一个元素)
s【i】数组:存的是之前第i个比他大的数的下标
z【i】:存的是之后第i个比他大的数的下标
我们从小到大枚举1----n-k+1之间的数,当我们计算完1个时需要把他从链表中删除,这样就可以保证每次枚举一个数时,他的左右全是比他大的数(比他小的数其实存在下标差中,并不是真的删掉,只是我们找的时候不需要找比他小的数)
举个例子:
2 1 3 5 4
下标: 1 2 3 4 5
枚举1的时候,1两侧全是比他大的数,然后把1删除变为
2 3 5 4
下标: 1 3 4 5
我们在枚举2时,2两侧还是只有比他大的数,但是当我们计算个数的时候进行下标运算,被删掉的1也会别计算在里边的
最后我们要分情况计算个数(前边有x个比他大,后边有k-1-x个比他的)
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=510000;
int a[N],pos[N];
LL ans;
int pre[N],np[N];
int s[N],s0;
int z[N],z0;
int n,k;
void erase(int x)
{
int pp=pre[x];
int nn=np[x];
if(pre[x])
np[pre[x]]=nn;
if(np[x]<=n)
pre[np[x]]=pp;
pre[x]=np[x]=0;
}
int main()
{
int t;
cin>>t;
while(t--)
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
pos[a[i]]=i;
}
for(int i=1;i<=n;i++)
pre[i]=i-1,np[i]=i+1;
ans=0;
for(int i=1;i<=n-k+1;i++)//从小到大枚举1~n-k+1中的数
{
int p=pos[i];
s0=0;
z0=0;
for(int d=p;d&&s0<=k+1;d=pre[d])//往前找k个比他大的数,并记录他的下标
s[++s0]=d;
for(int d=p;d!=n+1&&z0<=k+1;d=np[d])//往后找k个比他大的数,并记录他的下标
z[++z0]=d;
s[++s0]=0;//前边比他大的数<k的时候,方便计算排头比他小的数的个数
z[++z0]=n+1;//后边比他大的数<k的时候,方便计算末尾比他小的数的个数
for(int j=1;j<=s0-1;j++)//枚举情况数前边有j-1个后边有k-(j+1)个
{
if(k+1-j<=z0-1&&k+1-j>=1)
ans+=(z[k+1-j+1]-z[k+1-j])*1LL*(s[j]-s[j+1])*i;
}
erase(p);
}
cout<<ans<<endl;
}
return 0;
}
using namespace std;
typedef long long LL;
const int N=510000;
int a[N],pos[N];
LL ans;
int pre[N],np[N];
int s[N],s0;
int z[N],z0;
int n,k;
void erase(int x)
{
int pp=pre[x];
int nn=np[x];
if(pre[x])
np[pre[x]]=nn;
if(np[x]<=n)
pre[np[x]]=pp;
pre[x]=np[x]=0;
}
int main()
{
int t;
cin>>t;
while(t--)
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
pos[a[i]]=i;
}
for(int i=1;i<=n;i++)
pre[i]=i-1,np[i]=i+1;
ans=0;
for(int i=1;i<=n-k+1;i++)//从小到大枚举1~n-k+1中的数
{
int p=pos[i];
s0=0;
z0=0;
for(int d=p;d&&s0<=k+1;d=pre[d])//往前找k个比他大的数,并记录他的下标
s[++s0]=d;
for(int d=p;d!=n+1&&z0<=k+1;d=np[d])//往后找k个比他大的数,并记录他的下标
z[++z0]=d;
s[++s0]=0;//前边比他大的数<k的时候,方便计算排头比他小的数的个数
z[++z0]=n+1;//后边比他大的数<k的时候,方便计算末尾比他小的数的个数
for(int j=1;j<=s0-1;j++)//枚举情况数前边有j-1个后边有k-(j+1)个
{
if(k+1-j<=z0-1&&k+1-j>=1)
ans+=(z[k+1-j+1]-z[k+1-j])*1LL*(s[j]-s[j+1])*i;
}
erase(p);
}
cout<<ans<<endl;
}
return 0;
}