Description
给定一长度为 (n) 的数列 (a),可将 (a_i) 改为任意整数 (k),代价为 (mid a_i-kmid)。
问最少改变多少个数能把它变成一个单调严格上升的序列。
输出最少需要改变的数的个数,以及在改变的数最少的情况下,最小的代价和。
(1leq nleq 3.5 imes 10^4,1leq a_ileq 10^5)。
Solution
Part 1
Solve Problem 1:需要改变的数最少,则需保留的数要尽可能多。考虑取一个补集,问题转化为求最多保留多少个数。
对于两个数 (a_i,a_j)(不妨设 (i<j)),若可同时保留 (i) 和 (j),则 (a_i,a_j) 需满足:
-
(a_i<a_j)(显然)。
-
改变 ([i+1,j-1]) 内的数能够使 ([i,j]) 严格单调上升。所以 (a_j-a_igeq j-i)。移项可得,(a_i-ileq a_j-j)。
构造数列 (b_i=a_i-i),问题转化为求 (b) 的最长不降子序列。可 (O(nlog n)) 求得。
Part 2
Solve Problem 2:使 (a) 单调上升的代价,就是使 (b) 单调不降的代价。
考虑在 (b) 的最长不降子序列中,任意两个相邻的元素。设它们 (b) 中的位置分别为 (l,r),则一定 不存在 (iin[l,r]),使得 (b_lleq b_ileq b_r)。否则取上 (i),保证合法,而且可以使最长不降子序列更长。
所以对于 (forall iin[l,r]),(b_i<b_l) 或 (b_i>b_r)。
考虑如何改变 (b_i) 的值,能使序列合法且代价和最小。
结论:存在一个数 (kin [l,r]):对于 (forall iin[l,k]),把 (b_i) 改成 (b_l)。对于 (forall iin[k+1,r]),把 (b_i) 改成 (b_r)。此时代价和最小。
假设 ([l,r]) 之间有 (n) 个数。
-
当 (n=1) 时,结论显然成立。(因为 (b_i<b_l) 或 (b_i>b_r),(b_i) 改为 (b_l) 或 (b_r) 显然比改为取值在 ([b_l,b_r]) 之间的数优)
-
当 (n>1) 时:
-
前 (n-1) 个数一半改为 (b_l) 一半改为 (b_r):当 (b_n>b_r) 时,显然将 (b_n) 改为 (b_r) 比较优。当 (b_n<b_l) 时,若 (b_n) 不改为 (b_r) 改为了 (b_l+k)((0leq kleq b_r-b_l)),为了使序列单调不降,前面所有改为 (b_r) 的数都应改成 (b_l+k)(设这样的数有 (x) 个)。(x imes (b_r-(b_l+k))+((b_l+k)-b_n)=xb_r-(x-1)(b_l+k)-b_ngeq b_r-b_n),所以此时 (b_n) 改为 (b_r) 更优。
-
前 (n-1) 个数全改为 (b_l) 或 (b_r):略。
-
Part 3
令 ({dp}_i) 表示最后一位是 (b_i) 时单调不降的最小代价。
枚举 (j),枚举的 (j) 需满足:
-
(j<i,b_j<b_i)。
-
以 (b_j) 结尾的最长不降子序列长度 (=) 以 (b_i) 结尾的 (-1)。
枚举分界点 (k),有:
(displaystyle{dp}_i=min{{dp}_j+sumlimits_{p=j+1}^kmid b_p-b_jmid+sumlimits_{p=k+1}^{i-1}mid b_p-b_imid})
即:对于 (pin[j+1,k]),将 (b_p) 改为 (b_j)。对于 (pin[k+1,i-1]),将 (b_p) 改为 (b_i)。
前缀和优化转移即可。
Code
#include<bits/stdc++.h> #define int long long using namespace std; const int N=4e4+5; int n,a[N],b[N],len,f[N],g[N],dp[N],pre[N],suf[N]; vector<int>v[N]; //v[i]: 记录长度为 i 的最长不降子序列的结尾 void solve(int l,int r,int L,int R){ pre[l]=suf[r+1]=0; for(int i=l+1;i<=r;i++) //前缀和 pre[i]=pre[i-1]+abs(b[i]-L); for(int i=r;i>=l+1;i--) //后缀和 suf[i]=suf[i+1]+abs(b[i]-R); } signed main(){ scanf("%lld",&n); for(int i=1;i<=n;i++) scanf("%lld",&a[i]),b[i]=a[i]-i; b[0]=-1e9,b[++n]=1e9; //边界。加上前后最小值最大值方便操作。最大值不要设太大,不然算前缀和的时候可能会爆。 f[1]=b[1],len=1,g[1]=1,v[1].push_back(1); for(int i=2;i<=n;i++){ //O(n log n) 求最长不降子序列 if(b[i]>=f[len]) f[++len]=b[i],g[i]=len; else{ int x=upper_bound(f+1,f+1+len,b[i])-f; f[x]=b[i],g[i]=x; //g[i]: 以第 i 个数结尾的最长不降子序列的长度 } } for(int i=0;i<=n;i++) v[g[i]].push_back(i); memset(dp,0x3f,sizeof(dp)),dp[0]=0; for(int i=1;i<=n;i++) for(int p=0;p<(int)v[g[i]-1].size();p++){ //如果 b[j] 要拼上前面合适的 b[i],就去前面找长度为 g[i]-1 且能拼上的 int j=v[g[i]-1][p]; //以 b[j] 结尾的 最长不降子序列长度 = 以 b[i] 结尾的 -1 if(j>i||b[j]>b[i]) continue; //j<i,b[j]<=b[i] 才行 solve(j,i,b[j],b[i]); for(int k=j;k<i;k++) //枚举分界点 k dp[i]=min(dp[i],dp[j]+pre[k]+suf[k+1]); } printf("%lld %lld ",n-len,dp[n]); return 0; }