[atcoder caddi]E

题目大意:

给定(n)个正整数(a_i),每次可以将一个数乘以-2,求最小的操作次数使得最后的序列单调不降。

思路:

最后的序列一定是前面为负数,后面为正数。
于是我们枚举正数负数的分割点,这样操作就只有乘4一种了,现在问题转化为用最小的操作次数将一段前缀变成单调不升和一段后缀变成单调不降的。
前缀和后缀的情况类似,现在考虑前缀:不难发现每添加一个新的点(i),前面的数就要选择一截乘([j,i-1])乘4,也就是上升到新的数的上面,这个时候(a_{i-1}ge a_i)
如果原来(a_{i-1}geq a_i),那么这个时候(a_i)便有一段上升的空间,以后后面的数要上升的时候可以到(a_i)这里就停了。
于是发现我们要维护其实就是一个阶梯状的图案,每次将高度较小的抬升一段距离,这个直接用栈来维护即可。

#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
#define debug(x) cout<<#x<<"="<<x<<" "
#define pii pair<ll,ll>
#define fi first
#define se second
#define mk make_pair
#define pb push_back
typedef long long ll;

using namespace std;

void File(){
	freopen("e.in","r",stdin);
	freopen("e.out","w",stdout);
}

template<typename T>void read(T &_){
	_=0; T f=1; char c=getchar();
	for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
	for(;isdigit(c);c=getchar())_=(_<<1)+(_<<3)+(c^'0');
	_*=f;
}

const int maxn=2e5+10;
int n;
ll a[maxn],f[maxn],g[maxn],ans=1e18;

int main(){
	File();
	read(n);
	REP(i,1,n)read(a[i]);

	stack<pii>s1;
	REP(i,2,n){
		f[i]=f[i-1];
		ll pre=a[i-1],now=a[i];
		while(pre<a[i]){
			pre*=4;
			if(s1.empty())f[i]+=(i-1)*2;
			else{
				pii p=s1.top(); s1.pop();
				f[i]+=(i-p.fi)*2;
				if(--p.se)s1.push(p);
			}
		}
		ll cnt=0;
		while(now*4<=pre)now*=4,++cnt;
		if(cnt)s1.push(mk(i,cnt));
	}

	reverse(a+1,a+n+1);

	stack<pii>s2;
	REP(i,2,n){
		g[i]=g[i-1];
		ll pre=a[i-1],now=a[i];
		while(pre<a[i]){
			pre*=4;
			if(s2.empty())g[i]+=(i-1)*2;
			else{
				pii p=s2.top(); s2.pop();
				g[i]+=(i-p.fi)*2;
				if(--p.se)s2.push(p);
			}
		}
		ll cnt=0;
		while(now*4<=pre)now*=4,++cnt;
		if(cnt)s2.push(mk(i,cnt));
	}

	reverse(g+1,g+n+1);

	REP(i,0,n)ans=min(ans,f[i]+g[i+1]+i);

	printf("%lld
",ans);

	return 0;
}

原文地址:https://www.cnblogs.com/ylsoi/p/10165209.html