「CH5105」Cookies 题解

DP 好题

Statement

M \(\le 5000\) 块饼干分给 N \(\le 30\) 个人,每个人至少分得一块。

给定长为 N 的序列 \(g\) ,定义一种分配方式的权值为 \(\sum g[i]\times a[i]\)\(a[i]\) 表示全局中比 \(i\) 分的饼干多的人数

求最小权值和并构造一种方案。

Solution

一个很显然的贪心是,\(g[i]\) 大的,应该尽量分得更多的饼干

考虑先把 \(g\) 从小到大排序,那么饼干数量不增

\(c[i]\) 表示第 \(i\) 个人获得的饼干数量,则问题变成了构造一个长度 \(n\) 的单调不增的序列 \(c\)\(\sum c=m\)

考虑 DP ,发现需要的信息有点多,需要当前分配了几个人、一共分了多少块、这个人分了多少块、与这个人分的块数相同的人数

记录最后一个信息是为了计算 \(a\)

虽然发现分的块数只有 \(\sqrt n\) 种,在经过一些尝试过后,仍然发现状态数量无法缩减,GG

还有一个想法是说考虑费用提前计算的思想,也不行哈哈

以下思路来自:秦淮岸灯火阑珊

继续观察状态中的主要矛盾是什么,注意到 分配了几个人 分出多少块 ,肯定是需要的

而后续我们为了计算贡献而设的两个状态太难缠了/fn/fn/fn

考虑我们在算贡献的时候实际上需要的其实是一种偏序关系,我们并不关心具体到底选了什么数

然后出现了 本题中最为优雅的一步 ,我们直接设 \(f[i][j]\) 表示前 \(i\) 个人分了 \(j\) 块的最小代价

假设当前这个人分的块数 \(>1\),那么可以发现 \(f[i][j]=f[i][j-i]\) ,即把每一个人都砍一块

此时所有都可以被化归到当前这个人只选 \(1\) 块的情况中,此时,我们再枚举前面有几个人也只有 \(1\) 块即可,此时枚举量直接降了两维,即:

\[f[i][j]=\begin{cases} f[i][j-i]\\ f[k][j-i+k]+k\times \sum_{p=k+1}^ig[p],\\ \end{cases} \]

复杂度 \(O(n^2m)\)

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 55;
const int M = 5005;

char buf[1<<23],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read(){
	int s=0,w=1; char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
	while(isdigit(ch))s=s*10+(ch^48),ch=getchar();
	return s*w;
}
bool cmin(int&a,int b){return a>b?a=b,1:0;}

int f[N][M],sum[N],pre[N][M];
int g[N],p[N],ans[N];
int n,m;

void dfs(int i,int j){
	if(!i||!j)return ;
	int x=pre[i][j]/M,y=pre[i][j]%M;
	dfs(x,y);
	if(x==i)
		for(int k=1;k<=n;++k)
			ans[k]++;
	else for(int k=x+1;k<=i;++k)
		ans[p[k]]=1;
}

signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;++i)
		g[i]=read(),p[i]=i;
	sort(p+1,p+1+n,[](int a,int b)
		{return g[a]>g[b];});
	for(int i=1;i<=n;++i)
		sum[i]=g[p[i]]+sum[i-1];
	memset(f,0x3f,sizeof(f)),f[0][0]=0;
	for(int j=1;j<=m;++j){
		for(int i=1;i<=min(n,j);++i){
			if(j>=i)f[i][j]=f[i][j-i],pre[i][j]=i*M+(j-i);
			for(int k=0;k<i;++k)
				if(cmin(f[i][j],f[k][j-i+k]+k*(sum[i]-sum[k])))
					pre[i][j]=k*M+(j-i+k);
		}
	}
	dfs(n,m);
	printf("%lld\n",f[n][m]);
	for(int i=1;i<=n;++i)
		printf("%lld ",ans[i]);
	return 0;
}
posted @ 2022-05-25 13:27  _Famiglistimo  阅读(93)  评论(1编辑  收藏  举报