题目链接: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;
}