【2017 Multi-University Training Contest - Team 3】Kanade's sum

Link:http://acm.hdu.edu.cn/showproblem.php?pid=6058

Description

给你n个数;
它们是由(1..n)组成的排列;
然后给你一个数字k;
让你求这个序列的所有长度大于等于k的区间的第k大值的和;

Solution

数组模拟链表;
我们从小到大枚举数字x,寻找以x为第k大的数字的区间有多少个;
数组中只保留了了大于等于x的数字的信息
(即每次做完数字x为第k大的区间之后,把x删掉);
每次在做x的时候;
在x的左半部分找最近的比它大的k-1个数字,
然后从那第k-1个数字开始,将其位置设置为L,然后初始时刻,右端点位置R设置为x的位置;
因为模拟链表的数组中,存放的都是大于等于x的数字
所以当前的(L..R)这一段的第k大值肯定是数字x的;
(这时设L再往左跳一次会到达tL,R向右跳一次会到达tR);
(则答案加上x*(L-tL)*(tR-R),因为tL+1..L中任意一个数字作为区间左端点,R..rR-1中任意一个数字作为区间右端点,这些区间的k大值都是x)
然后左端点L肯定不能再往左移动了,不然x就不是第k大数了;
于是L往右移动一次;
同时R也跟着往右移动一次
(中间小于x的数字已经在之前的操作中删掉了,可以直接跳到下一个大于等于x的数字)
这样就能保证(L+1,R+1)这段区间也满足x是k大值;
再用上面的增加答案的方法增加答案就好;
直到L变成x为止
可能x左边没有k-1个大于它的数字;
那么就不用找那么多;
然后R提前先往右跳缺少(即却几次才够k-1个数字)次就好;

NumberOf WA

1

Reviw

利用链表这一工具,快速获取左边和右边最近的若干个大于某个数的位置;
再思考一下如何固定x为第k大值;
再利用乘法原理求出区间个数;
每个区间对答案贡献一样.
从而求出答案;

Code

#include <bits/stdc++.h>
using namespace std;
#define LL long long

const int N = 5e5;

int n,T,k,a[N+100],b[N+100][2],pos[N+100];

void del(int x){
    int pre = b[x][0],after = b[x][1];
    b[pre][1] = after;
    b[after][0] = pre;
}

int main(){
    scanf("%d",&T);
    while (T--){
        LL ans = 0;
        scanf("%d%d",&n,&k);
        for (int i = 1;i <= n;i++){
            scanf("%d",&a[i]),b[i][0] = i-1,b[i][1] = i+1;
            pos[a[i]] = i;
        }

        for (int x = 1;x <= n;x++){
            int i = pos[x],cntr = 0;
            for (int j = 1;j <= k-1;j++){
                int ti = b[i][1];
                if (ti > n) break;
                cntr++;
                i = ti;
            }

            int l = pos[x];
            for (int j = 1;j <= (k-1-cntr);j++)
                l = b[l][0];

            for (int j = i;j >= pos[x] && l;j = b[j][0],l = b[l][0])
                ans += 1LL*x*(l-( (b[l][0]) > 0 ? b[l][0]:0))*
                            ( (b[j][1] > n? (n+1):b[j][1]) - j);
            del(pos[x]);
        }

        printf("%lld\n",ans);
    }
    return 0;
}
posted @ 2017-10-04 18:44  AWCXV  阅读(97)  评论(0编辑  收藏  举报