Kanade's sum
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 417 Accepted Submission(s): 139
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
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
多校就是這麼喜歡出計數的題目……
這題還是比較好想到的,當場就A了。要你求所有區間的第k大之和。最開始還以為是一道資料結構的題目,什麼主席樹平衡樹樹套樹什麼的,然後發現好像根本沒必要,或者說起始那麼做更不好做。
還是比較慣用的伎倆,我們肯定不能枚舉出每一個區間,是以我們計算每一個數字作為區間第k大的貢獻。對于每一個數字,我們隻需要計算以他為區間第k大的區間的個數,然後用個數乘以這個數字再求和就是我們最後的結果。那麼問題來了,如何求區間個數呢?我們考慮該數字是區間第k大,是以在這個區間就有k-1個數字比該數字大。那麼我們就可以枚舉在該數字左邊有i個數字比它大,那麼在右邊就有k-1-i個數字比他小,我們對應的分别求出滿足條件的區間左端點數和區間右端點數,再相乘就是此時的貢獻。
這裡為了求左右端點的方案數,我們設定pos1和pos2數組。其中pos1[i]表示在該數字左邊第一個有i個比該數字大的區間的左端點,對應的pos2[i]表示在該數字右邊第一個有i個數字比該數字大的區間的右端點。如此一來,我們當我枚舉到左邊有i個比該數字大的數字的時候,左端點可選則的個數就是pos1[i]-pos1[i+1];那麼,右邊有k-1-i個比該數字大的時候,右端點可選擇的個數就是pos2[i+1]-pos2[i]。
#include<bits/stdc++.h>
#define LL long long
#define N 501000
using namespace std;
int a[N],nxt[N],pre[N],st[N],n,k;
int pos1[100],pos2[100];
LL ans;
int main()
{
int T_T;
cin>>T_T;
while(T_T--)
{
ans=0;
scanf("%d%d",&n,&k);
memset(pre,0,sizeof(pre));
memset(nxt,0,sizeof(nxt));
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
int tot=0; st[0]=0;
for(int i=1;i<=n;i++)
{
while (a[i]>a[st[tot]]&&tot) tot--; //單調棧預處理pre
pre[i]=st[tot]; st[++tot]=i;
}
tot=0; st[0]=0;
for(int i=n;i>0;i--)
{
while(a[i]>a[st[tot]]&&tot) tot--; //單調棧預處理nxt
nxt[i]=st[tot]; st[++tot]=i;
}
for(int i=1;i<=n;i++)
if (!nxt[i]) nxt[i]=n+1;
nxt[n+1]=n+1;
for(int i=1;i<=n;i++)
{
memset(pos1,0,sizeof(pos1));
memset(pos2,0,sizeof(pos2));
pos1[0]=pos2[0]=i;
for(int j=1;j<=k;j++) //計算兩個pos數組
{
int p=pos1[j-1]+1; //當期指針p指向上一個位置的後一位
while(a[p]<a[i]&&p<=n) p=nxt[p]; //如果該位置的數字小于a[i],那麼往後到它的nxt
pos1[j]=min(p,n+1); p=pos2[j-1]-1;
while(a[p]<a[i]&&p) p=pre[p]; //與上面同理,往前移到它的pre
pos2[j]=max(0,p);
}
for(int j=0;j<k;j++)
{
LL x=pos1[j+1]-pos1[j]; //這裡的pos與上面描述的pos相反(1是往後、2是往前)
LL y=pos2[k-j-1]-pos2[k-j]; //x、y分别代表左右端點的取值數量
ans+=x*y*(LL)a[i];
}
}
printf("%I64d\n",ans);
}
return 0;
}