hihoCoder #1661 数组区间
题目大意
给出 \(1\) 到 \(n\) 的一个排列(\(n\le 10^5\)),记做 \(a_1, a_2, \dots, a_n\) 。(注:原题面表述为:“给定 \(n\) 个互不相同且不超过 \(n\) 的整数”,并未指明 \(a_i\) 是正数,属描述不确切,实际题意如此。见管理员赛后发的题解)求所有可能的区间中前 \(k\) (\(k\le \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
要注意的点有:
std::list<T>::iterator
属于 bidirectional iterator ,仅支持++
和--
运算,不能加/减一个整数也不能两个 iterator 做差。- 要注意区别
std::list<T>::iterator
和std::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\n", *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 << '\n';
while(1){
++riter;
if(riter==a.rend()){
pre[c1]=0;
break;
}
// cout << *riter<<'\n';
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 << '\n';
// for(int i=1; i<=c2; i++)
// cout << post[i] << ' ';
// cout << '\n';
pre[0] = pos;
for(int j=1; j<=c1; j++){
// cout <<":: "<< i << ' ' << pre[j] << ' ' << post[min(c2,k+1-j)]<<'\n';
ans += (long long)(i)*(pre[j-1]-pre[j])*(post[min(c2,k+1-j)]-pos);
}
a.erase(iter[i]);
}
cout << ans << '\n';
return 0;
}