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\)

转移方程\(:\)

$f(i,j) = min\begin{cases}f(i,j-i)&{j≥i}\\min(f(k,j-(i-k))+\sum_{x=k+1}^ig_i×k)&{j≥i-k}\end{cases}$

\(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;
}






posted @ 2021-01-15 22:02  xcxc82  阅读(70)  评论(0编辑  收藏  举报