• hihoCoder #1661 数组区间


    题目大意

    给出 $1$ 到 $n$ 的一个排列($nle 10^5$),记做 $a_1, a_2, dots, a_n$ 。(注:原题面表述为:“给定 $n$ 个互不相同且不超过 $n$ 的整数”,并未指明 $a_i$ 是正数,属描述不确切,实际题意如此。见管理员赛后发的题解)求所有可能的区间中前 $k$ ($kle min(n,50)$)大的数之和的总和,对于长度不足 $k$ 的区间则全部累加。(注:前 $k$ 大是指“最大的 $k$ 个”)

    解法

    这是一道“标准的”区间统计类问题。不难想到思路:考虑 $a_i$ 在多少个区间内能成为前 $k$ 大的数之一。
    进一步转化成:

    对每个数 $a_i$,求它 前面/后面 离它最近且比它大的 $k$ 个数的下标。

    这个问题把我难住了。看了管理员的题解后,学到正解如下。

    按 $a_i$ 从小到大的顺序求解。原因在于:若 $a_j < a_i$ 则 $a_j$ 对 $a_i$ 的结果毫无影响,故而按此顺序求解,求出 $a_i$ 对应的答案过后便可将 $a_i$ 删除。不难想到,可以用“双向链表”来维护原序列;每次向前 $k$ 跳,再向后 $k$ 跳即可。复杂度 $O(nk)$ 。

    我并未手写双向链表,而是用了 std::list。我对 std::list 的接口不熟悉,这次又到 cppreference.com 上温习了一下。

    这道题用 std::list 要注意的点有:

    1. std::list<T>::iterator 属于 bidirectional iterator ,仅支持 ++-- 运算,不能加/减一个整数也不能两个 iterator 做差。
    2. 要注意区别 std::list<T>::iteratorstd::list<T>::reverse_iterator 这两个类型。二者貌似是可以互相做类型转换的。例如
    std::list<list> a{1,2,3,4};
    std::<list>::iterator it=a.begin();
    std::<list>::reverse_iterator rit = a.rend();
    std::<list>::reverse_iterator rit2(it);
    assert(rit == rit2);
    

    需要注意的是,当 iterator 转成 reverse_iterator 时,会向前移动一次,即返回的 reverse_iterator object 指向的是原 iterator object 所指向的前一位置。
    另外,在声明 reverse_iterator 时若要做此类型转换则只能以 (){} 的方式赋初始值不能以 = 的方式(至少 g++ 如此)。

    Implementation

    #include <bits/stdc++.h>
    using namespace std;
    list<int> a;
    const int N =1e5+5;
    list<int>::iterator iter[N];
    int pre[51];
    int post[51];
    int main(){
        int n, k;
        cin >> n >> k;
        for(int i=1; i<=n; i++){
            int x;
            cin >> x;
            // std::list<T>::iterator is a BidirectionalIterator and does not support arithmetic operation, it can only be incremented or decremented.
            a.push_back(i);
            auto tmp=a.end();
            iter[x]=--tmp;
            // printf("%d
    ", *iter[x]);
        }
        long long ans =0;
        for(int i=1; i<=n; i++){
            int c1 = 1;
            // auto tmp = iter[i];
            // how to convert an std::list<T>::iterator into an std::list<T>::reverse_iterator?
            // A: You can use std::make_reverse_iterator()
            // Note: the resulting reverse iterator is the original iterator moved one step forward, so that std::make_reverse_iterator(a.begin()) returns a.rend() and std::make_reverse_iterator(a.end()) returns a.rbegin().
            auto it = iter[i];
            // list<int>::reverse_iterator riter(++it);
            list<int>::reverse_iterator riter{++it};
            // cout << "x: "<< *riter << '
    ';
                while(1){
                    ++riter;
                    if(riter==a.rend()){
                        pre[c1]=0;
                        break;
                    }
                    // cout << *riter<<'
    ';
                    pre[c1] = *riter;
                    if(c1==k) break;
                    ++c1;
                }
            int c2=1;
            auto tmp = iter[i];
            while(1){
                ++tmp;
                if(tmp==a.end()){
                    post[c2]=n+1;
                    break;
                }
                post[c2]=*tmp;
                if(c2==k) break;
                ++c2;
            }
            assert(c2 <= k);
            int pos = *iter[i];
            // for(int i=1; i<=c1; i++)
            //     cout << pre[i] << ' ';
            // cout << '
    ';
    
            // for(int i=1; i<=c2; i++)
            //     cout << post[i] << ' ';
            // cout << '
    ';
            pre[0] = pos;
            for(int j=1; j<=c1; j++){
                // cout <<":: "<< i << ' ' << pre[j] << ' ' << post[min(c2,k+1-j)]<<'
    ';
                ans += (long long)(i)*(pre[j-1]-pre[j])*(post[min(c2,k+1-j)]-pos);
            }
            a.erase(iter[i]);
        }
        cout << ans << '
    ';
        return 0;
    }
    
  • 相关阅读:
    菜根谭#298
    菜根谭#297
    菜根谭#296
    菜根谭#295
    菜根谭#294
    菜根谭#293
    菜根谭#292
    菜根谭#291
    菜根谭#290
    菜根谭#289
  • 原文地址:https://www.cnblogs.com/Patt/p/8585964.html
Copyright © 2020-2023  润新知