POJ3666 Making the Grade

题意简述:

已知一个序列$A$ 找到一个单调不增或者单调不减的序列$B$ 使得$\sum_{i=1}^{n}|A_i-B_i|$最小

题解:

这题首先有一个引理

每一个$B_i$都在$A$中出现过

考虑一下简单的证明 设$A'$为$A$排序过后的序列 假如有一段$i~j$对应的$B$不是$A$的值而是夹在$A_i'$和$A_j'$中间的话 那么假设这一段中值比$A_i'$小的有$x$个 值比$A_j'$大的有$y$个 假如$y>x$ 那么我们平移这一段使得最大的到$A_j'$这条线上 那么和中位数问题一样 这样值会变得更小 所以$B$的数值肯定是在$A$中出现过的 那么我们可以考虑一个动态规划

设$f[i][j]$为考虑到第$i$个数对应的数值为$B_j$($B$是排序过后的$A$序列 这里由于可能单调不减或者单调不增所以要dp两次其实数据太水所以一遍就够了)

状态转移方程即为:$f[i][j]=min(f[i-1][k])+abs(a[i]-b[j])$ $(1\leq k\leq j)$

由于在$j$层循环$i$是不变的 所以我们可以考虑一个和最长公共上升序列一样的优化方法其实状态转移方程也很像

综上 时间复杂度为$O(n^2)$

AC代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define ll long long 
using namespace std;
const int maxn=2050;
const ll inf=1e10;
ll n,f[maxn][maxn],a[maxn],b[maxn];
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i],b[i]=a[i];
	sort(b+1,b+n+1);
	for(int i=1;i<=n;i++)
	{
		ll temp=inf;
		for(int j=1;j<=n;j++)
		{
			temp=min(temp,f[i-1][j]);
			f[i][j]=temp+abs(a[i]-b[j]);
		}
	}
	ll ans=inf;
	for(int i=1;i<=n;i++) ans=min(ans,f[n][i]);
	
	reverse(b+1,b+n+1);
	memset(f,0,sizeof(f));
	for(int i=1;i<=n;i++)
	{
		ll temp=inf;
		for(int j=1;j<=n;j++)
		{
			temp=min(temp,f[i-1][j]);
			f[i][j]=temp+abs(a[i]-b[j]);
		}
	}
	for(int i=1;i<=n;i++) ans=min(ans,f[n][i]);
	
	cout<<ans<<endl;
	return 0;
}