题意:圣诞老人共有\(M\)个饼干,准备全部分给\(N\)个孩子.每个孩子有一个贪婪度,第\(i\)个孩子的贪婪度为\(g[i]\).如果有\(a[i]\)个孩子拿到的饼干数比第\(i\)个孩子多,那么第 \(i\)个孩子会产生\(g[i]*a[i]\)的怨气.给定\(N、M\)和序列\(g\),圣诞老人请你帮他安排一种分配方式,使得每个孩子至少分到一块饼干,并且所有孩子的怨气总和最小,求最小怨气和以及任意一种方案.\(1≤N≤30,N≤M≤5000,1<=g[i]<=10^7\)
分析:设\(f[i][j]\)表示前i个人,分了j块饼干,所产生的最小的贪婪值.
先把所有人按照贪婪度从大到小排序,保证分的饼干数单调递减(这个贪心策略不要证明也能理解吧).
既然保证了单调性,那就好写转移方程.分两种情况讨论.
如果第i个人分的饼干数大于1,那么等价于前i个人分了j-i个饼干,即每个人少拿一块饼干(这样做相对大小不变,所以贪婪值也不会变,不影响结果):
\(f[i][j]=f[i][j-i]\)
如果第i个人获得的饼干数为1,则枚举i前面有多少个人也获得1块饼干:
\(f[i][j]=min_{0<=k<i} f[k,j-(i-k)]+k*\sum_{p=k+1}^i g[p]\)
初始化\(f[0][0]=0\),其余赋值为正无穷.目标\(f[n][m]\)
题目还要求方案,再开一个\(pre[i][j]\)表示前i个人,分了j块饼干时的转移路径.
如果\(f[i][j]=f[i][j-i]\),则\(pre[i][j]=i\)
如果是另一种情况,则\(pre[i][j]=k\)
如何输出方案时,同理也要分情况讨论,详见代码.
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s*w;
}
const int N=35;
int sum[N],ans[N],f[N][5005],pre[N][5005];
struct ppx{
int g,num;
}a[N];
inline bool cmp(const ppx &x,const ppx &y){return x.g>y.g;}
int main(){
int n=read(),m=read();
for(int i=1;i<=n;i++)a[i].g=read(),a[i].num=i;
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[i].g;
memset(f,0x3f,sizeof(f));f[0][0]=0;
for(int i=1;i<=n;i++)
for(int j=i;j<=m;j++){
f[i][j]=f[i][j-i];pre[i][j]=i;
for(int k=0;k<i;k++)
if(f[i][j]>f[k][j-(i-k)]+k*(sum[i]-sum[k])){
f[i][j]=f[k][j-(i-k)]+k*(sum[i]-sum[k]);
pre[i][j]=k;
}
}
printf("%d\n",f[n][m]);
//输出方案:(DP如何分类如何转移,输出方案时也同样做)
int nn=n,mm=m;
while(nn){
//如果是第一种情况,根据上述分析,把每个人的饼干数减1是等价的,所以每个人都可以分到一块饼干
if(pre[nn][mm]==nn){
for(int i=1;i<=nn;i++)ans[a[i].num]++;
mm-=nn;
}
//情况2,只有k+1到n分的饼干数一样,就一起处理,每个人都可以分到一块饼干.
else{
for(int i=pre[nn][mm]+1;i<=nn;i++)ans[a[i].num]++;
int k=pre[nn][mm];mm=mm-(nn-k);nn=k;
}
}
for(int i=1;i<=n;i++)printf("%d ",ans[i]);puts("");
return 0;
}