• BZOJ1049: [HAOI2006]数字序列


    Description

      现在我们有一个长度为n的整数序列A。但是它太不好看了,于是我们希望把它变成一个单调严格上升的序列。
    但是不希望改变过多的数,也不希望改变的幅度太大。

    Input

      第一行包含一个数n,接下来n个整数按顺序描述每一项的键值。n<=35000,保证所有数列是随机的

    Output

      第一行一个整数表示最少需要改变多少个数。 第二行一个整数,表示在改变的数最少的情况下,每个数改变
    的绝对值之和的最小值。

    Sample Input

    4
    5 2 3 5

    Sample Output

    1
    4

    Solution

    神仙题目。

    第一问考虑能保留下来多少,两个位置(i,j(i>j)),它们能保留下来当且仅当(a[i]-a[j]le i-j),移项得(a[i]-ile a[j]-j)。所以第一问构造一个新序列(b[i]=a[i]-i),答案就是n-新序列最长不下降子序列。

    第二问是神仙做法。对于任意({(i,j)|j<iwedge f[j]+1=f[i])}),显然只能改它们中间那一段使(b[i])不严格上升(因为保证了(f[j]+1=f[i])),有个结论,一定是有一个分割点(k),对于(xin [j,k])(b[x]=b[j]),对于(xin [k+1, i])(b[x]=b[i])

    可以感性的证明一下,假设我们现在有一种最优的修改方法,以(i)为横坐标,(b[i])为纵坐标,就可以得到几条阶梯状的线段(当前的修改方案),然后统计原(b[i])在线段上的点个数记为(up),原(b[i])在线段下的点个数记为(down),当一段线段上下(up=down)时,整条线段上移or下移都不会使答案更差,所以可以靠齐左边or右边。对于(up>down)的,可以让线段向下移动,(up<down)同理,向上移动,然后这样会使答案更优,成为更优的解法。所以最后的解法一定是这个结论里的摆法。(这个理解方法类似货场选址问题,大概是中位数相关)

    上方的感性证明有人画了个gif动图:https://www.luogu.org/blog/costudy/solution-p2501

    严谨一点的数学证明:https://www.luogu.org/blog/FlierKing/solution-p2501

    然后具体实现上,枚举(i),设(dp[i])表示以(i)结尾的(b)的非严格(LIS)长度,然后将(i)塞入(v[dp[i]])这个(vector),对于每个(i),遍历(vector),设当前位置为(v[j]),选(b[p]<b[i])的点来转移。先处理出(|b[p]-b[k]|)(|b[i]-b[k]|)的前缀和,然后可以枚举断点(k),转移方程即为(f[i]=min {f[i],f[p]+sum1[k]+sum2[i]-sum2[k]})。因为题目中就保证了数列随机,然后因为随机数列的(LIS)长度期望是(log)的,所以复杂度是期望(O(n^2log n))的,里面有个(n)是跑不满的,所以实际上也跑的挺快的...主要还是数据随机才跑得动...

    #include <bits/stdc++.h>
    using namespace std;
    
    #define ll long long
    const int N = 50010;
    const ll inf = 1e10;
    const int INTINF = 1e9;
    
    int dp[N], n, a[N], b[N];
    ll sum1[N], sum2[N];
    vector<int> v[N];
    ll f[N];
    
    int main() {
    	scanf("%d", &n);
    	for(int i = 1; i <= n; ++i) 
    		scanf("%d", &a[i]), a[i] -= i;
    	a[++n] = INTINF;
    	dp[1] = 1; b[1] = a[1];
    	int len = 1; 
    	for(int i = 2; i <= n; ++i) {
    		if(a[i] >= b[len]) b[++len] = a[i], dp[i] = len;
    		else {
    			int p = upper_bound(b + 1, b + len + 1, a[i]) - b;
    			b[p] = a[i]; dp[i] = p;
    		}
    	}
    	printf("%d
    ", n - len);
    	
    	a[0] = -INTINF;
    	v[0].push_back(0);
    	for(int i = 1; i <= n; ++i) {
    		f[i] = inf;
    		for(int j = 0, Len = v[dp[i] - 1].size(); j < Len; ++j) {
    			int p = v[dp[i] - 1][j];
    			if(a[p] > a[i]) continue;
    			sum1[p - 1] = sum2[p - 1] = 0;
    			for(int k = p; k <= i; ++k) {
    				sum1[k] = sum1[k - 1] + abs(a[i] - a[k]);
    				sum2[k] = sum2[k - 1] + abs(a[p] - a[k]);
    			}
    			for(int k = p; k <= i; ++k) {
    				f[i] = min(f[i], f[p] + sum2[k] - sum2[p] + sum1[i] - sum1[k]);
    			}
    		}
    		v[dp[i]].push_back(i);
    	}
    	printf("%lld
    ", f[n]);
    	return 0;
    }
    
  • 相关阅读:
    【Ubuntu使用技巧】在Ubuntu下制作ISO镜像的方法
    【Linux调试技术1】初步基础
    【算法研究与实现】最小二乘法直线拟合
    【嵌入式学习】移植konquerorembed
    【Asterisk应用】利用Asterisk产生呼叫的脚本
    【LDAP学习】OpenLDAP学习笔记
    一个.NET通用JSON解析/构建类的实现(c#)
    .net泛型在序列化、反序列化JSON数据中的应用
    C#字符串数组排序
    c#中的Json的序列化和反序列化
  • 原文地址:https://www.cnblogs.com/henry-1202/p/11201516.html
Copyright © 2020-2023  润新知