题意:
给出一个 n∗nn*nn∗n 的网格,其中有一些为黑,有一些为白。现在将一块 w∗hw*hw∗h 的区域全部涂成白色的花费是 max(w,h)max(w,h)max(w,h),问将整个区域全部涂成白色的最少花费是多少?(1≤n≤50)(1\leq n\leq 50)(1≤n≤50)
思路:
无论是从题干还是从数据范围,都可以比较明显地看出这是一道 DPDPDP。而 DPDPDP 问题最关键的就是状态怎么定,如果状态定对了,问题将迎刃而解。
比赛时的思路是 dp[i][j]dp[i][j]dp[i][j] 表示从 (1,1)(1,1)(1,1) 到 (i,j)(i,j)(i,j) 的区域全部涂成白色的最少花费。但是不知道如何转移这个状态,属于那种脑袋晕晕没有思路的状态,然后就放弃了…现在想想,当时发现状态没有很好的转移思路的时候,应该要及时更换思路,不能一条路走死了。
赛后知道了正确的状态应该是 dp[x1][y1][x2][y2]dp[x_1][y_1][x_2][y_2]dp[x1][y1][x2][y2],表示将 (x1,y1)(x_1,y_1)(x1,y1) 到 (x2,y2)(x_2,y_2)(x2,y2) 的区域全部涂成白色的最少花费。转移就直接横纵枚举网格分割点,记忆化搜索即可。
总结:
DPDPDP 的问题就是这样,想不出状态或想错状态的时候觉得这个问题真难,搞懂之后就开始嫌这题怎么简单,我当时怎么没做出来…(哭
反思一下的话,这题犯了两个比较明显的错误。第一个是一开始定完状态之后,发现不好转移然后没有再继续思考,而是直接放弃。缺乏状态定错进行转换的能力!
第二个是没有深挖题干。nnn 只有 505050,结果上来就只定一个二维状态,可真是浪费这么小的空间,犯了不仔细研究数据范围的大忌。这里必须再插一句,ACM\text{ACM}ACM 的世界里,题干和数据范围是非常重要的,没有数据范围空谈解决方法是极其不明智的,因此不充分利用好数据范围的信息,不根据数据范围来估算时间空间复杂度,属实属于 zzzzzz 行为。
50−O(n5),102−O(n4),500−O(n3),103−O(n2logn),104−O(n2),105/106−O(nlogn)50-O(n^5),10^2-O(n^4),500-O(n^3),10^3-O(n^2logn),10^4-O(n^2),10^5/10^6-O(nlogn)50−O(n5),102−O(n4),500−O(n3),103−O(n2logn),104−O(n2),105/106−O(nlogn)
代码:
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i = a; i <= b; i++)
const int N = 50+10;
using namespace std;
int n,sum[N][N],dp[N][N][N][N];
char s[N][N];
int dfs(int x1,int y1,int x2,int y2){
if(dp[x1][y1][x2][y2] != -1) return dp[x1][y1][x2][y2];
int cnt = sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1];
if(cnt == 0) return (dp[x1][y1][x2][y2] = 0);
int ans = max(y2-y1+1,x2-x1+1);
rep(i,y1,y2-1){
int t1 = dfs(x1,y1,x2,i);
int t2 = dfs(x1,i+1,x2,y2);
ans = min(ans,t1+t2);
}
rep(i,x1,x2-1){
int t1 = dfs(x1,y1,i,y2);
int t2 = dfs(i+1,y1,x2,y2);
ans = min(ans,t1+t2);
}
return (dp[x1][y1][x2][y2] = ans);
}
int main()
{
scanf("%d",&n);
rep(i,1,n) scanf("%s",s[i]+1);
rep(i,1,n)
rep(j,1,n){
sum[i][j] = sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1];
if(s[i][j] == '#') sum[i][j]++; //黑色块个数
}
memset(dp,-1,sizeof dp);
printf("%d\n",dfs(1,1,n,n));
return 0;
}