迎春舞会之三人组舞 vijos1061 动态规划

描述

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;
}

 

posted @ 2017-11-01 21:39  JayWang  阅读(325)  评论(0编辑  收藏  举报