[atcoder caddi]E - Negative Doubling——棧+貪心
阿新 • • 發佈:2018-12-23
題目大意:
給定\(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\n",ans); return 0; }