大致题意
有(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) = minegin{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
",f[n][m]);
print(n,m);
for(int i=1;i<=n;i++) printf("%d ",ans[i]);
return 0;
}