题意:
对一个数组a,定义其代价是 ∑ i = 1 n m e x { a 1 , a 2 , . . . , a i } \sum^{n}_{i=1}mex\{a_1,a_2,...,a_i\} ∑i=1nmex{a1,a2,...,ai}(对每个前缀数组求mex,结果求和)
给你n和a数组,可以将a数组进行循环转化,即把最后一个数放到数列最前面,或者把最前面的数放到末尾,a数组是0~n-1的一个排列,求最大代价
思路:
先说结论,这是个单调栈题
首先,如果0在数列中间,那么0前面的位置前缀的mex值肯定都是0(因为0空出位置了嘛),所以我们不妨直接把0放在最后,然后把最前面数放到最后,看看mex值是怎么变的。
5 2 4 1 3 0 0 0 0 0 0 6
2 4 1 3 0 5 0 0 0 0 5 6
4 1 3 0 5 2 0 0 0 2 2 6
1 3 0 5 2 4 0 0 2 2 4 6
3 0 5 2 4 1 0 1 1 1 1 6
0 5 2 4 1 3 1 1 1 1 3 6
很显然,是有规律的。仔细观察发现,如果加入的数很小,会把前面的大数给替换掉,换句话说,假如0现在在第i个位置上,现在在看第j个位置 ( 1 < j < n ) (1<j<n) (1<j<n)的mex值,那么这个位置的值总是是后n-j个数中最小的那个。
为什么呢?深入思考发现,因为a是个排列,换句话说,a的n个位置含有前n个数(0 ~ n-1),数与数之间没有空隙(就是说存在x和x+2时,一定也存在x+1,不存在中间没数的情况),而且数不会重复。所以除非对a数组全体数做mex,mex运算的结果一定只在0 ~ n-1里。因此我们取前j个数为一个集合,后n-j个数为另一个集合,两个集合合起来就是0~n-1的所有数。mex的结果一定在两个集合之一,不在前一个集合,就一定是后一个集合,因此mex的结果求的相当于是后n-j个数中的最小值。
所以我们在算一个场面的代价的时候,就是去算 ∑ j = i + 1 n − 1 m i n { a j + 1 , a j + 2 , . . . , a n } \sum_{j=i+1}^{n-1}min\{a_{j+1},a_{j+2},...,a_{n}\} ∑j=i+1n−1min{aj+1,aj+2,...,an}。
但是 n = 1 0 6 n=10^6 n=106,需要 O ( n l o g n ) O(nlogn) O(nlogn) 的算法,需要循环转化n次肯定是跑不了的,那么每次必须要在 l o g n logn logn的时间里计算,一个一个看j肯定是TLE的。发现如果后面如果有一个很小的数,那么前面的mex值一定就是这个数,中间的大一点的数就没用了,而小数后面的大一点的数有可能有用,这不是单调栈么。所以我们可以维护一个递增的单调栈,记录一下mex值和这个mex值的宽度,在维护单调栈的同时可以维护单调栈内的代价(也就是这个局面下的代价)这样就可以做到 O ( n ) O(n) O(n)了。
code:
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
const int maxn=1e6+5;
typedef long long ll;
int T,n,p[maxn<<1];
deque<pair<int,int> > s;//高度 宽度
int main(){
cin>>T;
while(T--){
cin>>n;
for(int i=0;i<n;i++){
cin>>p[i];
p[i+n]=p[i];
}
int bg;
for(int i=0;i<n;i++)
if(p[i]==0){
bg=i;
break;
}
while(!s.empty())s.pop_front();
ll ans=n,cur=n;
s.push_back(make_pair(0,1));
for(int i=bg+1,wid;i<bg+n;i++){
wid=1;
while(s.back().first>p[i]){
wid+=s.back().second;
cur-=1ll*s.back().first*s.back().second;
s.pop_back();
}
s.push_back(make_pair(p[i],wid));
cur+=1ll*s.back().first*s.back().second;
ans=max(ans,cur);
}
cout<<ans<<endl;
}
return 0;
}