「HAOI 2006」数字序列

description:

求解将 \(n\) 个数字组成的序列变成严格递增子序列的最小改变次数和最小代价,代价则为 \(a_i - k\)\(k\) 为改后的数字。$n\leq 3.5\times 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\) 。所以,对于任意 \(i\in [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\), 状态转移方程为 :

\[f_i = min(f_j+ w(j , k)) \]

\(w(j ,k)\) 表示将 \([j,i]\) 中 以 \(k\) 为分界点的最小代价,运用上述 \(Part \ \ 2\) 的结论,我们就能够得到, \(f_i\) 的递推式即为

\[f_i = min(f_j + \sum_{p = j + 1}^{k}|b_p - b_j| + \sum_{p = k + 1}^{i - 1}|b_p - b_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\n%lld\n" , n - lth , f[n]) ; 
	return 0 ; 
}
posted @ 2021-05-26 10:35  SkyFairy  阅读(84)  评论(0编辑  收藏  举报