描述
n个人选出3*m人,排成m组,每组3人。
站的队形——较矮的2个人站两侧,最高的站中间。
从对称学角度来欣赏,左右两个人的身高越接近,则这一组的“残疾程度”越低。
计算公式为 h=(a-b)^2 (a、b为较矮的2人的身高)
那么问题来了。现在候选人有n个人,要从他们当中选出3*m个人排舞蹈,要求总体的“残疾程度”最低。
显然是一道动态规划题目。(如果要求最大的残疾程度最小,可以二分解决)
一道经典的线性动态规划问题,其中也利用了一个贪心的思想。 核心:相邻最优+倒序处理 设f[I,j]为前i个人分成j 组的最小值,其中这里的前i个人为高度前i高的人,与题目的前i个人有所不同。 那么我们有这样的状态转移方程: F[I,j]=Min{f[i-1][j], f[i-2][j-1]+(a[i]-a[i-1])^2} 说明一下: 1.这里的f[i-1][j]表示不以较矮身份分i号人到组中 2.f[i-2][j-1]表示以a[i], a[i-1]为两个较矮的人分成一组(即当前此人和后面的一个人一组) 显然,在排好序后,取a,b时取相邻的总比取不相邻的要好。 这样我们就可通过计算公式求出“残疾程度”,那么就可以了。 而f[i-3,j-1]即默认了a[i-2], a[i-1]与a[i]形成一组,实际上残疾程度的计算与最高的人没有任何关系 所以是f[i-2,j-1]! 最关键的需要注意DP的方向。。 由于题目升序排序。。故而当前第i人是最高的。。这样会发生一个很悲剧的情况: 很有可能最后的几个二元组找不到补齐的第三人。。所以我们需要把数据倒过来处理。。 即i循环来倒推 这样就算最后几个人组成若干二元组。。由于n>=3*m成立所以必然可以找到对应的第三人。。 (这段话好好理解,对dp有很大帮助) 在有了a,b后,中间的那个人c,需要c>a,c>b,所以c的下标必在a,b前面,又因为a和b相邻 因此循环的时候要有j<=min(m,(n-i+1)/3)。 最后答案就是f[1][m] 嗯表示还是要好好多明白题目意思扩展思路 涨姿势了Orz 我写的代码是从后往前倒序的,所以状态转移方程有所不同 好好体会吧~
附上AC代码
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; template<class T> inline void read(T &_a){ bool f=0;int _ch=getchar();_a=0; while(_ch<'0' || _ch>'9'){if(_ch=='-')f=1;_ch=getchar();} while(_ch>='0' && _ch<='9'){_a=(_a<<1)+(_a<<3)+_ch-'0';_ch=getchar();} if(f)_a=-_a; } long long n,m,h[5001],dp[5001][1001]; int main() { read(m); read(n); for (register int i=n;i;--i) read(h[i]); memset(dp,0x7f,sizeof(dp)); for (register int i=1;i<=n;++i) dp[i][0]=0; for (register int i=1;i<=n;++i) for (register int v=1;v*3<=i&&v<=m;++v) dp[i][v]=min(dp[i-1][v],dp[i-2][v-1]+(h[i]-h[i-1])*(h[i]-h[i-1])); printf("%lld",dp[n][m]); return 0; }