题意:
给定一个整数序列,你可以选择其中一个元素-1,请问操作多少次能形成一个先严格递增后严格递减的序列,求出(操作次数+最大元素)的最小值
题解:
我第一感觉是二分最大元素的位置,实际上这个问题并没有二分性,最大元素的大小和(操作次数+最大元素)的值无关,所以假了。
但是思考过程中会发现,如果确定了最大元素,那么对元素左边和元素右边的操作实际上是一样的,如果能够低复杂度的计算操作次数,就可以直接得到答案。
通过几组数据的模拟,我们大致可以感觉到一种继承、递推的关系,以a[i]为最大元素和以a[i-1]为最大元素对应的操作次数存在一定的递推关系,我们可以从1跑到n取记录以a[i]对应的最小操作次数cnt[i],前文说了,元素左边和右边的操作是一样的,我们不妨先从左往右记录左边的cnt1:
当a[i]>a[i-1]时,cnt1[i]=cnt1[i-1],这是显然的。
当a[i]<a[i-1]时,我们怎么处理呢?朴素的思路是从i-1遍历到1,依次修改并记录操作次数,但是我们可以发现,如果我进行了操作,那么就会产生严格递减的子序列,而严格递减的连续子序列两个元素之间相差1,因为他是公差已知的等差序列,我们可以用长度和最大值来描述整个连续子序列,只需要记录最大值和长度,也就是当前点和区间长度就可以描述这个区间,其他冗余的点我们都可以去掉。
对这个连续子序列我们可以利用数学进行O(1)计算操作次数,根本不用回去遍历,他的操作次数就为len*(a[i]-a[i-1]),然后我们去除冗余的点,只记录a[i]和区间长度,之后我们要计算区间第一个数是否严格大于上一个数,也就是说是否要继续修改上一个区间,如果不严格大于的话,重复执行上述操作。如果出现第一项元素为负数的情况,证明我们减过头了,我们更新区间长度和操作次数即可。
然后我们把数组倒置再做一遍,得到数组L,R。Li,Ri分别表示将前(后)i个元素变成升序所需要的最小操作次数,遍历一遍取最值就是答案惹。
那么我们回过头来看看,把元素分段看成一段一段递减的子序列,现在需要维护的是连续子序列的最大值和长度,所以说有些点我们不用存储,我们只要存储最大的点和长度即可,我们怎么去除连续子序列冗余的点呢?很容易想到pop操作,我们用单调栈模拟这个问题就可以了
话不多说,上代码!
AC CODE:
#include<iostream>
#include<cmath>
#include<algorithm>
#include<map>
#include<vector>
#include<cstring>
#include<set>
#include<queue>
#include<iomanip>
#define eol "\n"
typedef long long ll;
typedef unsigned long long ull;
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
const int N = 2E6 + 7;
using namespace std;
ll ksm(ll a,ll b) {ll res=1;a%=mod; while(b){if(b&1)res=res*a%mod;b>>=1;a=a*a%mod;}return res%mod;}
ll gcd(ll a,ll b) {return b==0?a:gcd(b,a%b);}
ll lcm(ll a,ll b) {return a/gcd(a,b)*b;}
ll inv(ll x) {return ksm(x, mod-2);}
void solve(){
ll n;cin>>n;
vector<vector<ll>>v(3,vector<ll>(n+1));//v[1]存原序列,v[2]存翻转后的序列
for(int i=1;i<=n;i++){
cin>>v[1][i];
v[2][n-i+1]=v[1][i];
}
vector<vector<pair<ll,ll>>>stack(3);//同样的东西开两份
vector<vector<ll>>ans(3,vector<ll>(n+1));//同样的东西开两份
ll op[3]={0,0,0};//同样的东西开两份
for(int k=1;k<=2;k++) {//做两遍,一遍得出L数组,一遍得出R数组,k是当前在做第k份
for (int i = 1; i <= n; i++) {
ll len=1;
while(!stack[k].empty()&&v[k][i] - len+1<=stack[k].back().first){//如果说我的
op[k]+=( stack[k].back().first - v[k][i] + len)*stack[k].back().second;//更新操作次数
len+=stack[k].back().second;//更新区间长度
stack[k].pop_back();//pop冗余的元素
}
if(v[k][i] - len + 1<0){//首项<0,减过头了
op[k]-=(len-v[k][i]-1)*(len-v[k][i])>>1;//等差求和算多减了多少给他加回去
len=v[k][i];//更新区间长度
}
ans[k][i]=op[k];//记录Li(Ri)
stack[k].push_back({v[k][i],len});
}
}
ll ansv=1e18;
for(int i=1;i<=n;i++)
ansv=min(ansv,ans[1][i]+v[1][i]+ans[2][n-i+1]);//求最值
cout<<ansv<<eol;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int t=1;cin>>t;
while(t--)
solve();
}