饼干
题目链接:饼干
题目描述:
圣诞老人共有 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;
}