Cyclic MEX

文章讨论了一种关于数组变换代价的问题,利用单调栈的性质,将计算复杂度从O(nlogn)降低到O(n),通过维护mex值和宽度来快速确定每个阶段的代价,给出了一段C++代码实现。
摘要由CSDN通过智能技术生成

题目链接

题意:

对一个数组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+1n1min{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;
} 
  • 21
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值