題目鏈接:http://acm.hdu.edu.cn/showproblem.php?pid=6058
題目大意: 給定一個排列n,求所有區間第k小的和。
官方題解:我們只要求出對於一個數x左邊最近的k個比他大的和右邊最近k個比他大的,掃一下就可以知道有幾個區間的k大值是x.
我們考慮從小到大枚舉x,每次維護一個鏈表,鏈表裏只有>=x的數,那麼往左往右找只要暴力跳k次,刪除也是O(1)的。
時間複雜度:O(nk)
以前都沒做過鏈表的題。。 看的別人的代碼 還需要好好加強理解
//2017-08-2 14:08
//2017-08-2 14:36
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
using namespace std;
typedef long long LL;
const int MaxN = 5e5;
int T, n, k, temp;
int pos[MaxN + 5], pre[MaxN + 5], nxt[MaxN + 5];
LL a[MaxN + 5], b[MaxN + 5];
LL ans;
LL Getans(int x){
LL ret = 0;
int r1 = 0, r2 = 0;
for(int i = x; i >= 0 && r1 <= k; i = pre[i]) a[++r1] = i - pre[i];
for(int i = x; i <= n && r2 <= k; i = nxt[i]) b[++r2] = nxt[i] - i;
for(int i = 1; i <= r1; i++){
if(k - i + 1 <= r2 && k - i + 1 >= 1)
ret = ret + a[i] * b[k - i + 1];
}
return ret;
}
void del(int i){
pre[nxt[i]] = pre[i];
nxt[pre[i]] = nxt[i];
}
void solve(){
for(int i = 0; i <= n + 1; i++){
pre[i] = i - 1;
nxt[i] = i + 1;
}
pre[0] = 0;
for(int i = 1; i <= n; i++){
ans = ans + (Getans(pos[i]) * i);
del(pos[i]);
}
printf("%lld\n", ans);
}
int main(){
scanf("%d", &T);
while(T--){
ans = 0;
scanf("%d %d", &n, &k);
for(int i = 1; i <= n; i++){
scanf("%d", &temp);
pos[temp] = i;
}
solve();
}
return 0;
}