P4850 [IOI2009] 葡萄干 raisins 题解
为了纪念第一道蓝题,特来写一篇题解。
2022.8.25:更改了 LaTeX 格式。
直奔主题:
思路:
显然的,当有一个矩阵满足:
- 左上角坐标为
(a1,b1) ,右下角坐标为(a2,b2)
则这个矩阵的最优解为:
- 将这个矩阵切开(横、竖)后的两个矩阵的最优解中的最小值与这个矩阵的各元素之和。
如上,是横着切和竖着切的两种情况。
所以,我们可以 dfs 枚举每一刀切的位置。同时,用记忆化删去部分冗余的计算。定义一个数组
代码:
#include<bits/stdc++.h>//支持万能头文件!!
using namespace std;
int n,m;
int ra[60][60]; //每块巧克力上的葡萄干数量
int f[60][60][60][60]; //记忆化
int dfs(int a,int b,int c,int d)
{
if(f[a][b][c][d]!=0)//如果已经切过这种情况了
return f[a][b][c][d];//直接返回
if(b==d&&a==c) return 0;//如果开始点和结束点是同一点,即没法切了,就return
int ma=1e10;//赋一个极大值,以便后来作min运算
for(int i=a;i<c;i++) ma=min(ma,dfs(a,b,i,d)+dfs(i+1,b,c,d));//横着切,即上边加下边
for(int i=b;i<d;i++) ma=min(ma,dfs(a,b,c,i)+dfs(a,i+1,c,d));//竖着切,即左边加右边
for(int i=a;i<=c;i++)
for(int j=b;j<=d;j++)
ma+=ra[i][j];//要付出多少葡萄干
return f[a][b][c][d]=ma;
/*等价于:
f[a][b][c][d]=ma;
return f[a][b][c][d];*/
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>ra[i][j]; //循环输入葡萄干
}
}
cout<<dfs(1,1,n,m);
return 0;
}
这种做法开 O2 就可以 AC 了,但是,作为一个合格的 oier ,我们应该不断追求更好的做法。
优化思路:
因为考虑到每进行一次 dfs,就要算一遍葡萄干的和,所以很自然地想到利用前缀和数组来优化。
每输入一个数,则对其进行前缀和计算。就避免每次搜索都用一个双层循环来计算。
前缀和代码:
AC代码:
#include<bits/stdc++.h>//支持万能头文件!!
using namespace std;
int n,m;
int ra[60][60]; //每块巧克力上的葡萄干数量
int f[60][60][60][60]; //记忆化
int cc[60][60]; //前缀和数组
int dfs(int a,int b,int c,int d)
{
if(f[a][b][c][d]!=0)//如果已经切过这种情况了
return f[a][b][c][d];//直接返回
if(b==d&&a==c) return 0;//如果开始点和结束点是同一点,即没法切了,就return
int ma=1e10;
for(int i=a;i<c;i++) ma=min(ma,dfs(a,b,i,d)+dfs(i+1,b,c,d));//横着切,即上边加下边
for(int i=b;i<d;i++) ma=min(ma,dfs(a,b,c,i)+dfs(a,i+1,c,d));//竖着切,即左边加右边
f[a][b][c][d]=ma+cc[c][d]-cc[a-1][d]-cc[c][b-1]+cc[a-1][b-1];
//这里使用前缀和优化
return f[a][b][c][d];
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>ra[i][j]; //循环输入葡萄干
cc[i][j]=ra[i][j]+cc[i][j-1]+cc[i-1][j]-cc[i-1][j-1];//计算前缀和
}
}
cout<<dfs(1,1,n,m);
return 0;
}
这样不用 O2 就可以过了!
评测记录
拓展:
不会吧不会吧,不会还有人不知道二维前缀和怎么算吧!
核心代码:
其中,
我们可以用一个图形来理解一下:
整个面积就是
很显然:
当然,这不是严谨的推论,图也不标准,仅供参考。