饼干(算法竞赛进阶指南)
圣诞老人共有 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有所不同,以前的可以找到一个固定的趋势,但这道题明显更灵活。
换句话说,这道题的范围实在太大了,我们要想办法减少计算的范围。怎么办?
如何在所有分配方案中找到有着最优解的局部分配方案?用贪心的思想。
将所有小朋友按愤怒值g[i]从大小到小排序,排名靠前的小朋友分配的饼干要更多一些。
如果总有一些孩子无法满足,那么我们可以想到控制分饼干的数目,最后吃亏的孩子拿到的饼干数量是固定的,乘以贪婪度时显然贪婪度越低值越小。
首先将所有小朋友按g[i]从大到小排序。
这道题成功转化为先前类似的题,我们能感受到到转态转移的趋势是固定的。
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 31, M = 5010;
int n, m;
PII g[N];
int s[N];
int f[N][M];
int ans[N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
{
cin >> g[i].first;
g[i].second = i;
}
sort(g + 1, g + n + 1);
reverse(g + 1, g + n + 1);
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)f[i][j] = f[i][j - i];//给所有人都来一块饼干,不会产生怨气值
for(int k = 1;k <= i&&k <= j;k ++)
f[i][j] = min(f[i][j],f[i - k][j - k] + (s[i] - s[i - k]) * (i - k));//给1-k一块饼干,后面的人贡献怨气值
}
cout << f[n][m] << endl;
//求路径
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 <= j;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;
}
}
}
}
for (int i = 1; i <= n; i ++ ) cout << ans[i] << ' ';
cout << endl;
return 0;
}
本文作者:zychh
本文链接:https://www.cnblogs.com/zychh/p/16726677.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步