CH5105 Cookies[线性DP]
大概有一个初步状态的设计想法,第一维dp到第几个人,第二位dp发了多少饼干。但是人是杂乱无章的,无法进行dp。尝试将无序化为有序,看看可不可以排序。
发现越贪婪的人,我们希望他拿的饼干越多,因为少的话造成的代价大嘛,所以宁愿让贪婪度小的人去造成代价。
猜到最优方案一定是按贪婪度从大到小排序后从左到右分发饼干单调不增的。可以用微扰证明,比如假设在排序后的某个人之后的人分的饼干比这人多,发现剩下的人不会消去怨气可能会更多。对于两个人来说,通过自身贪婪度关系可以比较出这样一定是不优的。日常口胡证明毕。
所以有了顺序,$g$从大到小,dp。暴力思路是$f[i][j][k]$表示第$i$个人时发了$j$个,本人拿了$k$个的min代价。所以每次枚举$i,j,k$,再考虑和之前的大小关系,也就是枚举之前连续多少个人和他拿的饼干一样多,然后转移。
$f[i][j][k]=min\{f[i-l][j-l*k][p]+sum[i-l+1$~$i]*(i-l)\}$
然后会享受到时空双炸。然后就卡住了。。。。
lyd给的做法乍一看有点神仙。。根本想不到啊。。。但是仔细剖析一下,其本质就是对上面暴力的一种(等效)优化。优化功夫还不到家啊。。
发现原本枚举第$i$个人拿了$k$个饼干并向前枚举有多少人也拿了$k$个,这样其实是没有必要的多余计算。当第$i$个人取了$1$个饼干,向前直接枚举即可。
而假设要计算取了$k(k \geqslant 2)$个饼干的话呢,这种情况可以直接由之前推过的状态等效转移。所有人统一去掉$1$块饼干,是不是我之前推过的状态$(f[i][j-i][...])$?也就是说我之前的$j-i$块饼干分配的最优情况,再经过每人都发一块,其最优性不变,也就是$i$取了$k$个的时候的最优情况。(可以反证证明为什么之前的最优的统一加一块就是现在最优的)这等效于我暴力枚举$k$,再枚举人数。其本质是一种前缀min的不断继承。
所以状态得到简化 $f[i][j]$表示第$i$个人时发了$j$个的$min$代价。然后每次每个人由选$1$个(暴力dp)和选若干个(等效转移)中取min即可。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<queue> 7 #define dbg(x) cerr<<#x<<" = "<<x<<endl 8 #define ddbg(x,y) cerr<<#x<<" = "<<x<<" "<<#y<<" = "<<y<<endl 9 using namespace std; 10 typedef long long ll; 11 typedef pair<int,int> pii; 12 template<typename T>inline char MIN(T&A,T B){return A>B?A=B,1:0;} 13 template<typename T>inline char MAX(T&A,T B){return A<B?A=B,1:0;} 14 template<typename T>inline T _min(T A,T B){return A<B?A:B;} 15 template<typename T>inline T _max(T A,T B){return A>B?A:B;} 16 template<typename T>inline T read(T&x){ 17 x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1; 18 while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x; 19 } 20 const int N=30+2,M=5000+7;const ll INF=1ll<<40; 21 ll f[N][M],sum[N][N]; 22 int ans[N],n,m,cnt; 23 struct thxorz{ 24 int g,pos; 25 }A[N]; 26 pii h[N][M]; 27 inline char cmp(thxorz a,thxorz b){return a.g>b.g;} 28 29 int main(){//freopen("test.in","r",stdin);//freopen("test.out","w",stdout); 30 read(n),read(m); 31 for(register int i=1;i<=n;++i)read(A[i].g),A[i].pos=i; 32 sort(A+1,A+n+1,cmp); 33 for(register int i=1;i<=n;++i)sum[0][i]=sum[0][i-1]+A[i].g; 34 for(register int i=1;i<=n;++i)for(register int j=1;j<=i;++j)sum[j][i]=sum[0][i]-sum[0][j-1]; 35 for(register int i=1;i<=n;++i){ 36 for(register int j=1;j<i;++j)f[i][j]=INF;f[i][i]=0; 37 for(register int j=i+1;j<=m;++j){ 38 f[i][j]=f[i][j-i];h[i][j]=make_pair(i,j-i); 39 for(register int k=i-1;k;--k)if(MIN(f[i][j],sum[k+1][i]*k+f[k][j-(i-k)]))h[i][j]=make_pair(k,j-i+k); 40 } 41 } 42 printf("%lld\n",f[n][m]);int x=n; 43 while(x){ 44 if(h[x][m].first==x)++cnt; 45 else for(register int i=h[x][m].first+1;i<=x;++i)ans[A[i].pos]=cnt+1; 46 pii tmp=h[x][m];x=tmp.first,m=tmp.second; 47 } 48 for(register int i=1;i<=n;++i)printf("%d ",ans[i]); 49 return 0; 50 }