饼干_线性DP

饼干

题目链接:饼干
题目描述:
圣诞老人共有 M 个饼干,准备全部分给 N 个孩子。

每个孩子有一个贪婪度,第 i 个孩子的贪婪度为 g[i]。

如果有 a[i] 个孩子拿到的饼干数比第 i 个孩子多,那么第 i 个孩子会产生 g[i]×a[i] 的怨气。

给定 N、M 和序列 g,圣诞老人请你帮他安排一种分配方式,使得每个孩子至少分到一块饼干,并且所有孩子的怨气总和最小。

输入格式
第一行包含两个整数 N,M。

第二行包含 N 个整数表示 g1∼gN。

输出格式
第一行一个整数表示最小怨气总和。

第二行 N 个空格隔开的整数表示每个孩子分到的饼干数,若有多种方案,输出任意一种均可。

数据范围
1≤N≤30,
N≤M≤5000,
1≤gi≤107
输入样例:
3 20
1 2 3
输出样例:
2
2 9 9

      ~~~~~      要解决此题首先要先贪心,将集合进行缩小,然后才能求解,显而易见的在这里怨气值越大的若分到的饼干排位越小,起影响肯定越大,我们也能确定越大的怨气值肯定要分到的饼干越多,这里可以用排序不等式来证明,所以先将怨气值从大到小排序然后递减的分配饼干来dp
      ~~~~~      dp状态显而易见的二维,代表前i个人分配j块饼干,但是这里饼干数量其实对答案没影响,有影响的是饼干数量的排位,若是相邻之间饼干数量一致显然不会做贡献,那么要如何进行状态转移呢,我们可以以最后有多少个连续的饼干数量为1来进行划分,若是最后没有饼干数量为1的,这可以进行状态之间的等价转换,假设将所有人的饼干数量减1,那么就等于dp[i][j-i],由于相互之间排位没有变,所以状态是等价的,最终总会枚举到结尾有若干个1,注意这里饼干数量不在作为划分的依据,通常的二维dp方式已经不适用
在这里插入图片描述

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef pair<int,int> pii;
const int N=31,M=5010;
pii g[N];
int s[N];
int ans[N];
int f[N][M];
int n,m;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&g[i].first);
        g[i].second=i;
    }
    sort(g+1,g+1+n);
    reverse(g+1,g+1+n);
    for(int i=1;i<=n;i++)s[i]=s[i-1]+g[i].first;
    memset(f,0x3f,sizeof(f));
    f[0][0]=0;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
        if(j<i)continue;
        f[i][j]=f[i][j-i];
        for(int k=1;k<=i;k++)
        f[i][j]=min(f[i][j],f[i-k][j-k]+(s[i]-s[i-k])*(i-k));
    }
    int i=n,j=m,h=0;
    while(i&&j)
    {
        if(j>=i&&f[i][j]==f[i][j-i])j-=i,h++;
        else 
        {
            for(int k=1;k<=i;k++)
            if(f[i][j]==f[i-k][j-k]+(s[i]-s[i-k])*(i-k))
            {
                for(int u=i;u>i-k;u--)
                ans[g[u].second]=1+h;
                i-=k,j-=k;
                break;
            }
        }
    }
    cout<<f[n][m]<<endl;
    for(int i=1;i<=n;i++)cout<<ans[i]<<' ';
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值