cqyz oj | 【重庆市NOIP模拟赛】数据 | DP动态规划 | 堆优化

Description

  Mr_H出了一道信息学竞赛题,就是给 n 个数排序。输入格式是这样的:
  试题有若干组数据。每组数据的第一个是一个整数 n,表示总共有 n 个数待排序;接下来 n 个整数,分别表示这n个待排序的数。
  例如:3 4 2 ?1 4 1 2 3 4,就表示有两组数据。第一组有3个数(4,2,-1),第二组有4个数(1,2,3,4)。可是现在Mr_H做的输入数据出了一些问题。例如:2 1 9 3 2 按理说第一组数据有2个数(1,9),第二组数据有3个数,可是“3”后面并没有出现三个数,只出现了一个数“2”而已!
  现在Mr_H需要对数据进行修改,改动中“一步”的含义是对文件中的某一个数+1或-1,写个程序,计算最少需要多少步才能将数据改得合法。

Input

  第一行一个整数m,表示Mr_H做的输入数据包含的整数个数。第二行包含m个整数a[i],每个整数的绝对值不超过10000。

Output

  一个整数,表示把数据修改为合法的情况下,最少需要多少步。

Sample Input 1

4
1 9 3 2

Sample Output 1

2

Sample Input 2

10
4 4 3 5 0 -4 -2 -1 3 5

Sample Output 2

3

Hint

对于20%的数据,m<=10, |a[i]|<=5;
对于60%的数据,m<=5000, |a[i]|<=10000;
对于100%的数据,m<=100000, |a[i]|<=10000.


DP题,先设状态函数dp(i),表示将以第i个数结尾的数据修改为合法需要的最少步数
边界dp(0)=0
Ans=dp(m)
转移方程\(dp(i)=min\{dp(j)+|a[j+1]-(i-(j+1))|\} | 0<=j<i\)
表示以a[j+1~i]为一组数据,则要将a[j+1]的值改为i-(j+1),求所有的j中的最小值
直接枚举j时间复杂度O(m*m)超时。

优化:
打开绝对值内的括号\(dp(i)=min\{dp(j)+|a[j+1]+j+1-i|\} | 0<=j<i\)
再将绝对值展开得到

\[dp(i)=min \left\{ \begin{array}{} min\{dp(j)+a[j+1]+j+1\}-i | a[j+1]+j+1>=i ……①\\ min\{dp(j)-a[j+1]-j-1\}+i | a[j+1]+j+1<i ……②\\ \end{array} \right. \]

现在我们用val(j)表示dp(j)+a[j+1]+j+1,用x(j)表示a[j+1]+j+1
然后用一个堆,维护val(j)的最小值,将堆顶所有x(j)<i的弹出
计算最小的tmp = val(q.top()) - 2x(q.top()) = dp(j)+a[j+1]+j+1 - 2(a[j+1]+j+1) = dp(j) - a[j+1] - j - 1,
即为2式要维护的最小值,得到的最小tmp再+i即为满足2式条件的最优结果

因为i从小到大,所以满足x(j)小于当前i的j一定满足小于后面的i,
因此tmp不需要每次重新计算,只要每次和堆顶比较即可
弹出结束后如果堆不空,则堆顶满足x(q.top())>=i,又因为是按val()排序,
此时的val(q.top())-i即为满足1式条件的最优结果,然后dp(i)取上面得到的1式2式最优结果的最小值即可

以下思路来源:https://blog.csdn.net/its_elaine/article/details/76034801


对于这个思路,可能有疑惑的是,如果k满足x(k)<i,但因为val(k)的值较大而被压在堆里,是否会漏情况导致未得到2式的最优解。

结果是,不会。
证明:若出现上述情况,则设停止弹出时堆根为j,有val(j)<=val(k)且x(k)<i<=x(j)
将val(j)<=val(k)展开为dp(j) + x(j) <= dp(k) + x(k)
移项得dp(j) - dp(k) + x(j) <= x(k) ①
此时dp(i)由j转移,结果是dp(j) + x(j) - i。
如果由k转移,结果是dp(k) - x(k) + i。
上减下得dp(j) - dp(k) + x(j) + x(k) - 2i
由①得,该式<= 2
x(k) -2i
因为 x(k)<i,所以 2
x(k) -2*i < 0,所以该式<0
即由j转移比由k转移更优,证明了没有考虑k不会影响到dp(i)值的计算。


但是k仍有保留在堆中的价值,因为当i增大到x(j)<i时由j转移的值会按2式计算,与由k转移的值大小不能直接依靠上式推导出来,需要靠tmp取最小值来取舍。
转移部分代码(堆依靠优先队列实现):

struct data{
	int val;//dp[j]+a[j]+j+1
	int x;//a[j]+j+1
	bool operator<(const data &b)const{return val>b.val;}
};
priority_queue<data> q;
//main()
    dp[0]=0;
	int i=0;
	while(i<m){
		q.push((data){dp[i]+a[i+1]+i+1, a[i+1]+i+1});
		i++;
		while(!q.empty() && q.top().x<i){
			tmp=min(tmp,q.top().val-q.top().x*2);
			q.pop();
		}
		dp[i]=min(dp[i],tmp+i);
		if(!q.empty())dp[i]=min(dp[i],q.top().val-i);
	}
posted @ 2019-07-25 15:00  Deguassing-compass  阅读(221)  评论(0编辑  收藏  举报