AcWing277. 饼干
大致题意
有\(m\)块饼干,\(n\)个人,每个人都有一个贪婪度,第\(i\)个人的贪婪度为\(g_i\),如果有\(a_i\)个人拿到的饼干数比第\(i\)个人多,那么他就会产生\(g_i×a_i\)的怨气,求一种排列方式,使得每个人至少分到一块饼干且怨气和最小
\(n≤30,n≤m≤5000\)
分析
发现这个贡献计算是有后效性的,不太好搞
先来尝试猜个结论:越贪的人吃越多的饼干,用邻项交换易证。这样每个人分到的饼干数就是单调不上升的了
也就是说,要计算第\(i\)个人的贡献,我们只需要知道第\(i\)个人前面有几个人和他分到的饼干数相等,一个比较NAIVE的想法是设\(f(i,j,k)\)表示在前\(i\)个人中,一共分了\(j\)块饼干,第\(i\)个人分到\(k\)块时的最小怨气值和
转移也比较显然,枚举前面有几个人和他分到的饼干数相等:
\(f(i,j,k) = min\begin{cases}min(f(i-1,j-k,p)+g_i×(i-1))&{j-k≥p×(i-1),p>k})\\min(f(i-p,j-p×k,k)+\sum_{b=i-p+1}^ig_b×(i-p-1))&{j-p×k≥(i-p-1)×k}\end{cases}\)
时间复杂度\(O(nm^3)\)
数据似乎比较水(,可以拿个80pts左右的样子
现在来考虑优化
可以发现,我们只关心每个人分到的饼干数之间的相对大小,而不关心其具体数值,因此我们可以尝试去把前面那个状态的\(k\)维度去掉
设\(f(i,j)\)表示在前\(i\)个人中共分了\(j\)块饼干的最小怨气值和
如果第\(i\)个人分到的饼干数不为\(1\),前\(i\)个人共分到\(j\)块饼干等价于每人少分一块饼干,既\(f(i,j-i)\),因为每个人拿到的饼干数之间的相对大小没有变,总贡献也不会变
如果第\(i\)个人分到的饼干数为\(1\),那么就可以枚举他前面有多少个人分到的饼干数也为\(1\)
转移方程\(:\)
\(code\)
//xcxc82
/*
80pts
memset(f,0x3f,sizeof(f));
memset(f[1],0,sizeof(f[1]));
for(int i=2;i<=n;i++){
for(int j=1;j<=m;j++){
for(int k=1;k<j;k++){
for(int p=k+1;p<=j;p++){
if(j-k<p*(i-1)||j<k) continue;
f[i][j][k] = min(f[i][j][k],f[i-1][j-k][p]+g[i]*(i-1));
}
for(int p=1;p<=i-1;p++){//注意要特判p=i-1,j-p×k!=k的情况
if(j-p*k<(i-p-1)*k||j<p*k||(p==i-1&&j-p*k!=k)) continue;
f[i][j][k] = min(f[i][j][k],f[i-p][j-p*k][k]+(i-p-1)*(sum[i]-sum[i-p]));
}
}
}
}
*/
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5010;
int sum[MAXN],n,m,f[MAXN/100][MAXN];
int l1[MAXN/100][MAXN],l2[MAXN/100][MAXN],ans[MAXN/100];
struct cookie{
int g,ind;
}a[MAXN];
bool cmp(cookie x,cookie y){
return x.g>y.g;
}
int SUM(int i,int j){
return sum[j] - sum[i-1];
}
inline int read(){
int X=0; bool flag=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
if(flag) return X;return ~(X-1);
}
void print(int i,int j){
if(i==0) return;
print(l1[i][j],l2[i][j]);
if(i==l1[i][j]){
for(int x=1;x<=i;x++) ans[a[x].ind]++;
}
else for(int x=l1[i][j]+1;x<=i;x++) ans[a[x].ind] = 1;
}
int main(){
n = read(),m = read();
for(int i=1;i<=n;i++) a[i].g = read(),a[i].ind = 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++){
for(int k=0;k<i;k++){
if(f[k][j-(i-k)]+k*SUM(k+1,i)<f[i][j]){
f[i][j] = f[k][j-(i-k)]+k*SUM(k+1,i);
l1[i][j] = k,l2[i][j] = j-(i-k);
}
}
if(f[i][j-i]<f[i][j]){
f[i][j] = f[i][j-i];
l1[i][j] = i,l2[i][j] = j-i;
}
}
}
printf("%d\n",f[n][m]);
print(n,m);
for(int i=1;i<=n;i++) printf("%d ",ans[i]);
return 0;
}