HDU 6058 Kanade's sum

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

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]
(http://acm.hdu.edu.cn/search.php?field=problem&key=2017%20Multi-University%20Training%20Contest%20-%20Team%203&source=1&searchmode=source)

题意:

在长度为n、元素为1-n的数组中,找出每一个连续子区间内的第k大数,求出这些数的和。

想法:

1-n这些数是已知且少的,而区间个数是众多的,所以我们可以遍历数组中的每个数,求出每个数作为第k大数所在的区间个数, 区间个数×此时区间的第k大数就是最终的结果。

我们记当前遍历的数为num, pos[i]表示num所在的位置。
1.记录数组中每个数所在的位置,用数组构建一个双向链表。
2.从1作为num开始num++遍历,我们会发现num两边的数一定是比num大的。
我们在num的位置之前和之后分别找k个比它大的数(要找到第k个 因为num本身占一个位置)
3.找到一个num后,将其删掉,并且发现由于连续,位置的差值就是区间的个数。我们删掉的是数,这个位置依然存在。

举个栗子理解一下:
6 3
3 2 1 5 4 6
pos[3]=1 pos[2]=2 pos[1]=3 pos[5]=4 pos[4]=5 pos[6]=6
首先找到pos[1] 两边 2 4 比它大,然后我们删掉这个1
之后我们再找num=2,pos[2],两边是3 4 比它大,我们删掉这个2
以此类推 我们遍历完能成为第k大数的num

下面我们来寻找使4成为第3大数的可能(此时1 2 3已被删去)
其可能性有 左2右0 左1右1 左0右2 三种
在4之前比它大的数有 5 s[1]=5 s[2]=4(s[1]和t[1]存的是num自身的位置)
在4之后比它大的数有 6 t[1]=5 t[2]=6(s[2]和t[2]分别表示num之前和之后第一个大于它的数的位置)
都只有1个 所以只符合第二种可能
s[1]-s[2]+1=2 代表在4之前取左1右1 只有两种可能 即 1 5 4和5 4
t[2]-t[1]=1 代表在4之后取左1右1 只有一种可能 即4 6
最终4成为第3大数的区间只有1*2种
5 4 6
1 5 4 6

tips:
1.注意1LL的用法:为了在计算时,把int类型的变量转化为long long,然后再赋值给long long类型的变量。(在这里wa到心态爆炸QAQ)
2.题意即使懂了,实现起来因为牵扯到位置所以上下限很容易出问题,遍历的各个范围要很懂,不懂就手动模拟试一试XD

#include<cstdio>
using namespace std;
const int maxn=500010;
int pre[maxn],next[maxn],a[maxn],pos[maxn];
int s[maxn],t[maxn];//s[i],t[i]表示位置i上的数
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n,k;
        scanf("%d%d",&n,&k);
        for(int i=1; i<=n; ++i)
        {
            scanf("%d",&a[i]);
            pos[a[i]]=i;//当前位置
            pre[i]=i-1;//前驱
            next[i]=i+1;//后继
        }
        long long ans=0;
        for(int num=1; num<=n-k+1; ++num)//n-k+1? //枚举可能成为第k大的数(比如一个数列中最大的数不可能在任何子数列里成为第二大)
        {
            int p=pos[num];//当前查找的数的位置
            int ss=1,tt=1;//表示左边第ss个大的 右边第tt个大的 但ss[1]和tt[1]表示当前数本身的位置
            /*从当前数的位置向两边查找,各找k-1个比当前数大的使得当前数成为第k大*/
            for(int i=p; i&&ss<=k+1; i=pre[i])//
                s[ss++]=i;
            for(int j=p; j<=n&&tt<=k+1; j=next[j])
                t[tt++]=j;
            s[ss]=0;//在s末加上0表示结束
            t[tt]=n+1;//在t末加n+1表示结束
            /*任意从当前数之前拿scnt个.0从后拿tcnt个使得当前数成为第k大数*/
            for(int scnt=1; scnt<=ss-1; scnt++)//当前数之前比当前大的数里不包括这个数本身 而ss[1]表示当前数 所以要减去这个数
            {//scnt代表拿当前数之前比它大的scnt-1个数
                int tcnt=k-(scnt-1);//真正在之前有scnt-1个比当前数大的数
                if(tcnt<=tt-1&&tcnt>0)//tt-1与上面ss-1同理
                    ans+=(s[scnt]-s[scnt+1])*1LL*(t[tcnt+1]-t[tcnt])*num;//在当前数之前多余的数其实是第scnt个数 在之后多余的是第tcnt个数
/*当前数遍历完之后删除此节点的数 但位置还存在 因为此数如果小并不影响第k大数*/
                if(pre[p]>0)
                    next[pre[p]]=next[p];
                if(next[p]<=n)
                    pre[next[p]]=pre[p];
                pre[p]=next[p]=0;
            }
        }
        printf("%I64d\n",ans);
    }
    return 0;
}

ps:反正各个临界值一定要注意!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值