description:
求解将 (n) 个数字组成的序列变成严格递增子序列的最小改变次数和最小代价,代价则为 (a_i - k) , (k) 为改后的数字。$nleq 3.5 imes 10^4 , a_i leq 10^5 $
solution :
Part 1
第一问,求解最小改变次数 :
我们正着想,显然不是那么好想,考虑反过来去想,考虑一下最多更够有多少个被保留不更改,那么我们考虑一下 (a_i , a_j)不更改的条件显然是 :
- (a_i < a_j)
- 改变 ([i +1 , j- 1]) 中的数能够使得 ([i ,j]) 单调递增,那么就有 (a_i - a_j leq i - j) , 移项,就能够得到 (a_i - i leq a_j - j)
那么,我们构造数列 (b) , 使得 (b_i = a_i - i) , 就求解 (b) 的最长不下降子序列, (n - ret) 就是答案。
Part 2
显然难度就是在第二问上了。
使 (a) 单调递增的代价,和 (b) 单调不降的代价是一致的。
显然在 (b) 的单调递增的序列中,相邻的两个元素 (b_l , b_r) 在对于任意的 (i in [l ,r]) ,都不存在 (b_l leq b_i leq b_r) 。所以,对于任意 (iin [l ,r]) ,都有 $b_i < b_l or b_i > b_r $ , 那么这时候需要考虑一下 (b_i) 对于代价的影响,也就是,我们考虑怎么改变 (b_i) ,从而使得代价和最小。
有一个结论:
存在一个 (k in [l ,r]),并且在 (forall i in[l , k] , b_i = l , forall i in[k + 1 , r],b_i = b_r) 时,代价和最小。
我们发现,那么大个序列,只考虑一下开头结尾和中间一个值就能够解决吗,显然很突兀。
证明: 先略。
Part 3
设 (f_i) 表示最后一位是 (b_i) 时,单调不降的最小代价 。
枚举一下 (j) , (j) 需要满足如下条件 :
- (j < i ,b_j leq b_i)
- (i,j) 是上文中提到的,(b) 序列中单调不降的相邻的元素。
(k)显然不会特别地大,所以暴力枚举 (k) ,从而得到 (f_i), 状态转移方程为 :
(w(j ,k)) 表示将 ([j,i]) 中 以 (k) 为分界点的最小代价,运用上述 (Part 2) 的结论,我们就能够得到, (f_i) 的递推式即为
后面的东西可以用前缀和优化一下。
Code
/*
Author : Zmonarch
知识点
*/
#include <bits/stdc++.h>
#define int long long
#define qwq register
#define qaq inline
#define inf 2147483647
const int kmaxn = 1e6 + 10 ;
const int kmod = 998244353 ;
qaq int read(){
int x = 0 , f = 1 ; char ch = getchar() ;
while(!isdigit(ch)) {if(ch == '-') f = - 1 ; ch = getchar() ;}
while( isdigit(ch)) {x = x * 10 + ch - '0' ; ch = getchar() ;}
return x * f ;
}
int n , lth;
int minn[kmaxn] , f[kmaxn] , b[kmaxn];
int g[kmaxn] , pre[kmaxn] , suf[kmaxn] ;
std::vector<int> ed[kmaxn] ;
qaq void Sum(int l , int r , int L , int R) {
pre[l] = suf[r + 1] = 0 ;
for(qwq int i = l + 1 ; i <= r ; i++)
pre[i] = pre[i - 1] + std::abs(b[i] - L) ; // 前缀和
for(qwq int i = r ; i >= l + 1 ; i--)
suf[i] = suf[i + 1] + std::abs(b[i] - R) ; // 后缀和
}
signed main() {
n = read() + 1 ;
for(qwq int i = 1 ; i <= n - 1 ; i++) b[i] = read() - i ;
b[n] = inf , b[0] = - inf ;
minn[1] = b[1] , lth = 1 , g[1] = 1 , ed[1].push_back(1) ;
for(qwq int i = 2 ; i <= n ; i++) // 求解最长不下降子序列。O(nlogn)
{
if(b[i] >= minn[lth]) minn[++lth] = b[i] , g[i] = lth ;
else
{
int x = std::upper_bound(minn + 1 , minn + 1 + lth , b[i]) - minn ;
minn[x] = b[i] ; g[i] = x ;
}
}
for(qwq int i = 0 ; i <= n ; i++) ed[g[i]].push_back(i) ; // 找到先驱
memset(f , 63 , sizeof(f)) ; f[0] = 0 ;
for(qwq int i = 1 ; i <= n ; i++)
{
for(qwq int p = 0 ; p < (int)ed[g[i] - 1].size() ; p++)
{
int j = ed[g[i] - 1][p] ; // 去寻找在单调不下降子序列中相邻的那个地方
if(i < j || b[j] > b[i]) continue ; // 判断合法性。
Sum(j , i , b[j] , b[i]) ; // 利用前缀和优化
for(qwq int k = j ; k < i ;k++)
f[i] = std::min(f[i] , f[j] + pre[k] + suf[k + 1]) ; // 直接状态转移
}
}
printf("%lld
%lld
" , n - lth , f[n]) ;
return 0 ;
}