题意:圣诞老人共有(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
",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;
}