220 likes | 433 Views
HNOI 2004 day2 《 打砖块 》 解题报告. 题目描述. 在一个凹槽中放置了 n 层砖块,最上面的一层有 n 块砖,第二层有 n-1 块, …… ,最下面一层仅有一块砖。第 i 层的砖块从左至右编号为 1 , 2 , …… , i ,第 i 层的第 j 块砖有一个价值 a[i,j] ( a[i,j]<=50 )。 如果要敲掉第 i 层的第 j 块砖的话,若 i=1 ,可以直接敲掉它,若 i>1 ,则必须先敲掉第 i-1 层的第 j 和第 j+1 块砖。
E N D
题目描述 • 在一个凹槽中放置了n层砖块,最上面的一层有n块砖,第二层有n-1块,……,最下面一层仅有一块砖。第i层的砖块从左至右编号为1,2,……,i,第i层的第j块砖有一个价值a[i,j](a[i,j]<=50)。如果要敲掉第i层的第j块砖的话,若i=1,可以直接敲掉它,若i>1,则必须先敲掉第i-1层的第j和第j+1块砖。 • 你的任务是从一个有n(n<=50)层的砖块堆中,敲掉(m<=500)块砖,使得被敲掉的这些砖块的价值总和最大。
一个具体的例子(m=5) 2 2 3 4 8 2 7 2 3 49 和 2+3+4+2+7=18
一个具体的例子(m=5)’ 2 2 3 4 8 2 7 2 3 49 和 2+3+4+2+8=19 最优解!!!!
初步分析 • 首先我们会发现这道题跟一道经典题目数字三角形很相似,原题是从上到下(只能向左下或右下走)找一条通路,使所得权和最大 • 这道题初步构造,设f[I,j,k]为取第i行第j个共选了k个的最大权值 • 状态如何转移?
转移??? • 我们会发现有重复节点 • 层数越深,重复越多,不符合无后效性 • f[i-1,j,q]+f[i-1,j+1,p-q-1]+a[i,j],f[i,j,p]
怎么办? • 没有条件创造条件!!! • 我们之所以不能很好地解决问题是因为我们原来以每一个横行为阶段看待问题的角度不符合无后效性 • 转化~~~换个角度看问题
转化-换个角度看问题 2 2 3 4 8 2 7 2 3 49 按斜线划分状态
2 2 2 3 4 8 2 8 2 7 2 2 3 2 3 49 3 7 4 49 旋转~问题如此简单
转化-改变砖块的横纵坐标编号 • for i:= 1 to n do for j:= 1 to n-i+1 do • begin read(a[i,j]); b[i+j-1,j]:=a[i,j]; end; • for i:= 1 to n do • begin • p:=1; q:=i; • while p<=q do • begin • t:=b[i,p]; • b[i,p]:=b[i,q]; • b[i,q]:=t; • inc(p); • dec(q); • end; • end;
最终形态 2 8 2 2 2 3 49 3 7 4 从左往右打
状态转移 • 将原问题转化为格式化后的图形 • 设f[I,j,k]表示打到第i行,总共打了j个方块,其中第i行打了k个方块 • 则f[I,j,k]:=f[i-1,j-k,p]+sum[I,k]; • 1<=i<=n 1<=j<=m • 1<=k<=I K-1<=p<=I • Sum[I,k]表示第i行前k个数的和
至此,问题基本解决 • 时间复杂度O(n^3*m) • 空间复杂度O(n^2*m) • 编程复杂度~~易 • 思维复杂度~~中 • 在时限1s内轻松ac
细节问题 • 做到这里,我发现一个细节问题,那就是阶段m,j要从0开始枚举,因为一个数也不打这个也是可能滴
具体方程 • fillchar(sum,sizeof(sum),0); • for i:= 1 to n do • for k:= 1 to i do sum[i,k]:=sum[i,k-1]+b[i,k]; • fillchar(f,sizeof(f),255); • for i:= 0 to n do f[i,0,0]:=0; • for i:= 1 to n do f[i,1,1]:=b[i,1]; • for i:= 1 to n do • for k:= 0 to i do • for j:= 0 to m do • if k<=j then • for p:= k-1 to i-1 do • if f[i-1,j-k,p]<>-1 then • begin • f[i,j,k]:=max(f[i-1,j-k,p]+sum[i,k],f[i,j,k]); • if (j=m) and (f[i,j,k]>ans) then ans:=f[i,j,k]; • end;
测试结果 • 第一题:brick ∷ 得分 = 100.0 ∷ 用时 = 1.38s • brick-0(brike1.in) ∷ 结果 = 正确 ∷ 得分 = 10.0 ∷ 用时 = 0.03s ∷ 空间 = 9.21M • brick-1(brike2.in) ∷ 结果 = 正确 ∷ 得分 = 10.0 ∷ 用时 = 0.03s ∷ 空间 = 9.21M • brick-2(brike3.in) ∷ 结果 = 正确 ∷ 得分 = 10.0 ∷ 用时 = 0.05s ∷ 空间 = 9.21M • brick-3(brike4.in) ∷ 结果 = 正确 ∷ 得分 = 10.0 ∷ 用时 = 0.09s ∷ 空间 = 9.21M • brick-4(brike5.in) ∷ 结果 = 正确 ∷ 得分 = 10.0 ∷ 用时 = 0.13s ∷ 空间 = 9.21M • brick-5(brike6.in) ∷ 结果 = 正确 ∷ 得分 = 10.0 ∷ 用时 = 0.18s ∷ 空间 = 9.21M • brick-6(brike7.in) ∷ 结果 = 正确 ∷ 得分 = 10.0 ∷ 用时 = 0.22s ∷ 空间 = 9.21M • brick-7(brike8.in) ∷ 结果 = 正确 ∷ 得分 = 10.0 ∷ 用时 = 0.22s ∷ 空间 = 9.21M • brick-8(brike9.in) ∷ 结果 = 正确 ∷ 得分 = 10.0 ∷ 用时 = 0.22s ∷ 空间 = 9.21M • brick-9(brike10.in) ∷ 结果 = 正确 ∷ 得分 = 10.0 ∷ 用时 = 0.21s ∷ 空间 = 9.21M 最大用时为0.22s 还是很大
思考 • 我们在行与行之间状态转移的时候无端浪费了很多时间重复计算上一阶段最大值,不是吗?
优化 • G[I,j,k]表示max{f[I,j,k’]} (k’>=k) • f[I,j,k]:=max{g[i-1,j-k,k-1]+sum[I,k]} • 讲k的每局顺序逆序,可以做到更新g数组是用O(1)的时间 • 这样优化后的理论时间复杂度是 O(n^2*m)
总结 • 事实上这道题今天看来也并不是很难 • 需要积极思考的是: • A. 转化->旋转的思想 • B. 总前k个数 vs 本阶段前k个数 • C. 细节->阶段枚举到了 • D. 用记忆化优化状态转移方程 • E. 初始化的艺术
测试结果 • brick-0(brike1.in) ∷ 结果 = 正确 ∷ 得分 = 10.0 ∷ 用时 = 0.03s ∷ 空间 = 18.95M • brick-1(brike2.in) ∷ 结果 = 正确 ∷ 得分 = 10.0 ∷ 用时 = 0.03s ∷ 空间 = 18.95M • brick-2(brike3.in) ∷ 结果 = 正确 ∷ 得分 = 10.0 ∷ 用时 = 0.02s ∷ 空间 = 18.95M • brick-3(brike4.in) ∷ 结果 = 正确 ∷ 得分 = 10.0 ∷ 用时 = 0.04s ∷ 空间 = 18.95M • brick-4(brike5.in) ∷ 结果 = 正确 ∷ 得分 = 10.0 ∷ 用时 = 0.07s ∷ 空间 = 18.95M • brick-5(brike6.in) ∷ 结果 = 正确 ∷ 得分 = 10.0 ∷ 用时 = 0.08s ∷ 空间 = 18.95M • brick-6(brike7.in) ∷ 结果 = 正确 ∷ 得分 = 10.0 ∷ 用时 = 0.08s ∷ 空间 = 18.95M • brick-7(brike8.in) ∷ 结果 = 正确 ∷ 得分 = 10.0 ∷ 用时 = 0.07s ∷ 空间 = 18.95M • brick-8(brike9.in) ∷ 结果 = 正确 ∷ 得分 = 10.0 ∷ 用时 = 0.06s ∷ 空间 = 18.95M • brick-9(brike10.in) ∷ 结果 = 正确 ∷ 得分 = 10.0 ∷ 用时 = 0.06s ∷ 空间 = 18.95M 用时<0.1s,效果8错~~
谢谢 Starea.cn