「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]\)
假設當前這個人分的塊數 \(>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; }