• 「HAOI 2006」数字序列


    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), 状态转移方程为 :

    [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
    %lld
    " , n - lth , f[n]) ; 
    	return 0 ; 
    }
    
  • 相关阅读:
    Java IO流-NIO简介
    Java IO流-Properties
    Java IO流-序列化流和反序列化流
    Codeforces Round #371 (Div. 1) C
    bzoj 2326 矩阵快速幂
    IndiaHacks 2016
    HDU
    Educational Codeforces Round 51 (Rated for Div. 2) F
    Codeforces Round #345 (Div. 1) D
    Codeforces Round #300 E
  • 原文地址:https://www.cnblogs.com/Zmonarch/p/14812326.html
Copyright © 2020-2023  润新知