vijos 1061 迎春舞会之三人组舞

题目

背景

H N S D F Z HNSDFZ HNSDFZ的同学们为了庆祝春节,准备排练一场舞

描述

n n n个人选出 3 m 3m 3m个人,排成 m m m组,每组 3 3 3人。
站的队形为较矮的 2 2 2个人站两侧,最高的站中间。
从对称学角度来欣赏,左右两个人的身高越接近,则这一组的“残疾程度”越低。
计算公式为 h = ( a − b ) 2 h=(a-b)^2 h=(ab)2 ( a , b a,b a,b为较矮的2人的身高)
那么问题来了。
现在候选人有 n n n个人,要从他们当中选出 3 m 3m 3m个人排舞蹈,要求总体的“残疾程度”最低。

格式

输入格式

第一行为 m , n m,n m,n
第二行 n n n个数字,为每个人的身高,保证升序排列。

输出格式

输出最小“残疾程度”。

样例输入输出

输入

9 40
1 8 10 16 19 22 27 33 36 40 47 52 56 61 63 71 72 75 81 81 84 88 96 98 103 110 113 118 124 128 129 134 134 139 148 157 157 160 162 164

输出

23

限制

各个测试点 1 s 1s 1s

提示

m ≤ 1000 , n ≤ 5000 m\le1000,n\le5000 m1000,n5000
数据保证 3 m ≤ n 3m\le n 3mn

思路

思路一

这很明显是一道 d p dp dp的题,不难想到 d p [ i , j ] dp[i,j] dp[i,j]表示前 i i i个人组成 j j j个队伍的状态定义。
暴力枚举最后一个组,考虑的因素很多:最高的肯定是每一组中间的那一个,那么最高的可以直接拿来当高子。可是第二高的呢?他有没有可能跟着最高的组成队伍呢?是个问题。开个 b o o l bool bool数组记录?那就变回了 d f s dfs dfs,想都不用想, T L E TLE TLE

思路二

如果在最优答案中出现了这样的组合:
( a , b , e ) (a,b,e) (a,b,e) ( c , d , f ) ( a , b < e (c,d,f)(a,b<e (c,d,f)(a,b<e c , d < f c,d<f c,d<f a < c < b < d ) a<c<b<d) a<c<b<d) h h h的和为 ( b − a ) 2 + ( d − c ) 2 (b-a)^2+(d-c)^2 (ba)2+(dc)2
那么将其替换为
( a , c , e ) (a,c,e) (a,c,e) ( b , d , f ) (b,d,f) (b,d,f)
仍有 a < c < b < e , b < d < f a<c<b<e,b<d<f a<c<b<e,b<d<f
所以h的和为 ( c − a ) 2 + ( d − b ) 2 (c-a)^2+(d-b)^2 (ca)2+(db)2
因为 c − a < b − a c-a<b-a ca<ba d − b < d − c d-b<d-c db<dc
所以 h h h的和一定变小了
如图所示:黑色肯定没有红色的组合好(别忘了保证升序;延伸向右边的线连着高子)黑色肯定没有红色的组合好
所以我选完某一个组合之后,两个矮子中间的就不能再当矮子了。
那高子怎么定?转换思路: d p [ i , j ] dp[i,j] dp[i,j]表示后 i i i个人,而非前 i i i个人
这样剩下的就自然成为了高子咯!
所以状态转移方程就变成了
d p [ i , j ] = m i n ( d p [ k + 1 , j − 1 ] + ( a [ k ] − a [ i ] ) 2 , d p [ i + 1 , j ] ) dp[i,j]=min(dp[k+1,j-1]+(a[k]-a[i])^2,dp[i+1,j]) dp[i,j]=min(dp[k+1,j1]+(a[k]a[i])2,dp[i+1,j])
辅以一点点小技巧:滚动数组+斜率优化。大佬肯定早就不稀罕用了
实现见代码一。

思路三

这还是有点难啊……就没有一个更简单的方法吗?
当然有!听说下雨天,dp和贪心更配哦
倘若你选了第i个人,并在 i + 1 i+1 i+1 n n n中寻找他的队友时,你有没有想过,为什么不直接让 i i i i + 1 i+1 i+1匹配呢?你要是选了 k ( k > i + 1 ) k(k>i+1) k(k>i+1) i + 1 i+1 i+1 k − 1 k-1 k1之间没有人当矮子,你为什么不让 i + 1 i+1 i+1当矮子?
至于高子的问题,你只要让 3 j ≤ i 3j\le i 3ji就可以了,这样选完之后自动会剩下几个用来当高子。
(其实此处证明的不够严谨,但是你可以想象到:最前面的 j − 1 j-1 j1组必然占用 3 j − 3 3j-3 3j3个人,再加上我们现在正在计算的这两个矮子,共有 3 j − 1 3j-1 3j1个人会被使用。那么除去这些人,剩下的就是 i − 3 j + 1 i-3j+1 i3j+1个人,只要 3 j ≤ i 3j\le i 3ji i − 3 j + 1 > 0 i-3j+1>0 i3j+1>0,就一定还有人没有被占用,而 i i i i + 1 i+1 i+1是右数i个中最矮的两个,所以剩下的那个人可以当高子)
实现见代码二

代码

代码一

#include <cstdio>
#include <iostream>
using namespace std;
   
typedef long long LL;
const LL llong_inf=(1ll<<62)-1;
# define For(i,a,b) for(register int i=(a);i<=(b);++i)
# define Rep(i,a,b) for(register int i=(a);i>=(b);--i)
   
inline int readint(){
    int a=0;char c=getchar();bool f=0;
    while(c<'0'||c>'9'){if(c=='-')f=1;c=getchar();}
    while('0'<=c&&c<='9') a=a*10+c-'0',c=getchar();
    if(f) a=-a;return a;
}
inline LL Pow(const LL &x){
    return x*x;
}
  
struct Point{
    LL x,y;
    Point(LL X=0,LL Y=0):x(X),y(Y){}
};
 
Point operator-(const Point &a,const Point &b){
    return Point(a.x-b.x,a.y-b.y);
}
 
LL operator*(const Point &a,const Point &b){
    return a.x*b.y-b.x*a.y;
}
 
const int MAXN=5002;
int num[MAXN],q[MAXN],head,tail;
LL dp[MAXN][2];
Point point[MAXN];
 
void Push(const int &Index){
    Point now,last;
    while(tail-1>=head)
    {
        now=point[Index]-point[q[tail]];
        last=point[q[tail]]-point[q[tail-1]];
        if(now*last>=0)
            --tail;
        else
            break;
    }
    q[++tail]=Index;
}
  
void Maintain(const Point &xielv){
    Point now;
    while(head+1<=tail)
    {
        now=point[q[head+1]]-point[q[head]];
        if(now*xielv>=0)
            ++head;
        else
            break;
    }
}
  
int main()
{
    int m=readint(),n=readint();
    For(i,1,n) num[i]=readint();
    dp[n-1][1]=llong_inf;
    Rep(i,n-2,1) dp[i][1]=min(dp[i+1][1],Pow(num[i+1]-num[i]));
    int now_dp=1;
    For(j,2,m)
    {
        now_dp^=1;
        head=1,tail=0;
        int origin=n-3*j+1;
        point[origin]=Point(2*num[origin],dp[origin+1][now_dp^1]+Pow(num[origin]));
        dp[origin][now_dp]=dp[origin+2][now_dp^1]+Pow(num[origin+1]-num[origin]);
        Rep(i,origin-1,1)
        {
            Push(i+1);
            Maintain(Point(1,num[i]));
            dp[i][now_dp]=dp[q[head]+1][now_dp^1]+Pow(q[head]-num[i]);
            dp[i][now_dp]=min(dp[i][now_dp],dp[i+1][now_dp]);
            point[i]=Point(2*num[i],dp[i+1][now_dp^1]+Pow(num[i]));
        }
    }
    cout<<dp[1][now_dp];
    return 0;
}

代码二

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
    
inline int readint(){int x;scanf("%d",&x);return x;}
inline long long sqr(const long long &x){return x*x;}//pow(x,2)

const int MAXN=5000,MAXM=1000;const long long llong_inf=(1ll<<61)-1;
long long dp[MAXN+2][MAXM+2];int a[MAXN+2];

int main()
{
    int m=readint(),n=readint();
    for(int i=1;i<=n;++i)
    {
		a[i]=readint();
		for(int j=1;j<=m;++j)
			dp[i][j]=llong_inf;
	}
    for(int j=1;j<=m;++j)
    	for(int i=n-3*j+1;i>=1;--i)
    		dp[i][j]=min(dp[i][j],min(dp[i+2][j-1]+sqr(a[i+1]-a[i]),dp[i+1][j]));
    long long ans=llong_inf;
    for(int i=n-3*m+1;i>=1;--i)
    	ans=min(ans,dp[i][m]);
    printf("%lld",ans);
    return 0;
}

后记

其实一开始并没有想到简单的思路三,而是真的老老实实地打了一个斜率优化的 d p dp dp……只能说,好的思路造就较少代码量与较低错误率,但学的东西多了以后,就算不是正解思路也能搞出个名堂来!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值