1. 程式人生 > 其它 >「CH5105」Cookies 題解

「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;
}