是很有趣的一道题 : )
Sol
第一反应就是f[i][j]表示前i个小朋友分j块饼干的最小怨气值
但是一个孩子所产生的怨气值并不固定,它与其他孩子获得饼干的情况有关
这里可以用到一个贪心,就是贪婪度大的孩子应该获得尽量多的饼干
所以先按照贪婪度由大到小排序,那么获得的饼干数也会是非严格单调递减的
然而发现很还是难转移,因为这样直接转移需要前一个孩子获得的饼干数与比前一个孩子获得饼干多的孩子数
在现有的DP状态下,很难高效维护这两种信息
对状态做一个等价转化:
1.若第i个孩子获得的饼干数大于1
可以将所有的孩子获得的饼干同时减去1,它们的相对值并没有改变,所以这样所得到的答案任然是正确的
$f[i][j]=f[i][j-i]$
2.若第i个孩子获得的饼干数等于1
就要枚举k(1<=k<=i)表示在i前获得饼干数为1的第一个孩子是谁
$f[i][j]=min(f[k][j-(i-k+1)+(k-1)*\sum_{t=k}^{i}g[t])$
最后,这题还要记录答案。
在DP中需要给出方案时,一般的做法是额外使用一些与DP状态大小相同的数组记录下来每个状态的最优解是从何处转移而来的.最终,在DP求出最优解后,通过一次递归,沿着记录的每一步回到初态,即可得到一条从初态到最优解的转移路径,也就是所求的具体方案.
这题的答案统计与一般的题目不太一样,要细心.
Code
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<vector> 5 #include<algorithm> 6 #define Rg register 7 #define il inline 8 #define db double 9 #define ll long long 10 #define inf 2100000000 11 #define go(i,a,b) for(Rg int i=a;i<=b;++i) 12 #define yes(i,a,b) for(Rg int i=a;i>=b;--i) 13 using namespace std; 14 il int read() 15 { 16 int x=0,y=1;char c=getchar(); 17 while(c<'0'||c>'9'){if(c=='-')y=-1;c=getchar();} 18 while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();} 19 return x*y; 20 } 21 struct node{int dat,pos;}g[31]; 22 struct node1{int i,j;}a[31][5001]; 23 il bool cmp(node x,node y){return x.dat>y.dat;} 24 int n,m,ans[31],f[31][5001]; 25 int main() 26 { 27 n=read(),m=read(); 28 go(i,0,n)go(j,0,m)f[i][j]=inf;f[0][0]=0; 29 go(i,1,n)g[i].dat=read(),g[i].pos=i; 30 sort(g+1,g+n+1,cmp); 31 go(i,2,n)g[i].dat+=g[i-1].dat; 32 go(i,1,n) 33 go(j,i,m) 34 { 35 f[i][j]=f[i][j-i];a[i][j]=(node1){i,j-i}; 36 go(k,1,i) 37 if(f[i][j]>f[k-1][j-(i-k+1)]+(k-1)*(g[i].dat-g[k-1].dat)) 38 f[i][j]=f[k-1][j-(i-k+1)]+(k-1)*(g[i].dat-g[k-1].dat),a[i][j]=(node1){k-1,j-(i-k+1)}; 39 } 40 int t1=n,t2=m; 41 while(t1) 42 { 43 if(t1==a[t1][t2].i){go(i,1,t1)ans[g[i].pos]++;} 44 else{go(i,a[t1][t2].i+1,t1)ans[g[i].pos]++;} 45 int tt=t1;t1=a[t1][t2].i;t2=a[tt][t2].j; 46 } 47 printf("%d\n",f[n][m]); 48 go(i,1,n)printf("%d ",ans[i]); 49 return 0; 50 }