1. 题面梗概
给你一个 N×N 的网格。你要给它重新染色,使得它满足两个条件:
-
行约束: 每一行必须是“左边全是白,右边全是黑”。
-
列约束: 每一列必须是“上边全是白,下边全是黑”。
问:最少需要改动多少个格子的颜色?
2. 逻辑推导
2.1 分析
从行约束入手,设第 i 行分界点为 ki,即 (i,1⋯ki) 是白色,(i,ki+1⋯N) 是黑色。
接着考虑列约束,如果格子 (i,j) 是黑色,那么它下方的所有格子 (i+1,j),(i+2,j)… 都是黑色。
这说明了什么?说明如果第 i 行从第 j 列开始变黑,那么第 i+1 行在第 j 列也必须是黑的。这就意味着:ki+1≤ki,k 数组是非递增的序列。
结论:问题的本质就是寻找一组 k1,k2,…,kn,使得修改代价最少。
2.2 DP
遇到这种情况,我们考虑用 DP 来解决最优化问题。
定义:dp[i][j] 表示处理完前 i 行,且第 i 行分界点为 j 时的最小代价。
转移:dp[i][j]=Cost(i,j)+mink=jN(dp[i−1][k])。
- 其中 Cost(i,j) 表示把第 i 行变为 前 j 白,后 N−j 黑 的代价,这一项可以用前缀和快速查询。
- 然后就是 mink=jN(dp[i−1][k]) 这一项,前面提到 k 数组是非递增的,所以我们只需要从后面的转移。
瓶颈:如果暴力转移势必会在 min 这一项中浪费大量时间,导致复杂度退化为 O(N3),那样就 TLE 了。
观察:
⎩⎨⎧dp[i][n]=Cost(i,n)+min(dp[i][n])dp[i][n−1]=Cost(i,n−1)+min(dp[i][n−1],dp[i][n])dp[i][n−2]=Cost(i,n−2)+min(dp[i][n−2],dp[i][n−1],dp[i][n])
发现了吗?mink=jN(dp[i−1][k]) 是一个典型的后缀最小值,所以在处理每一行时,我们只需要预处理出上一行 DP 的后缀最小值数组,即可将转移复杂度从 O(N3) 降维到 O(N2)。
3. 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| #include <bits/stdc++.h> using namespace std; using ll = long long; using PII = pair<int, int>;
const int maxn = 5e3 + 50; const ll inf = 1e18;
char g[maxn][maxn]; ll dp[maxn][maxn]; ll sum[maxn][maxn]; ll tmp[maxn]; int n;
ll get_cost( int i, int j ) { ll cnt1 = sum[i][j]; ll cnt2 = ( n - j ) - ( sum[i][n] - sum[i][j] ); return cnt1 + cnt2; }
int main( ) { ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n; for( int i = 1; i <= n; ++ i ) { for( int j = 1; j <= n; ++ j ) { cin >> g[i][j]; sum[i][j] = sum[i][j-1]; if( g[i][j] == '#' ) sum[i][j] ++; } } for( int j = 0; j <= n; ++ j ) { dp[0][j] = 0; }
for( int i = 1; i <= n; ++ i ) { tmp[n] = dp[i-1][n]; for( int j = n - 1; j >= 0; -- j ) { tmp[j] = min( tmp[j+1], dp[i-1][j] ); } for( int j = 0; j <= n; ++ j ) { dp[i][j] = tmp[j] + get_cost( i, j ); } }
ll ans = inf; for( int j = 0; j <= n; ++ j ) { ans = min( ans, dp[n][j] ); } cout << ans << "\n";
return 0; }
|
4. 复盘