题面
题解
设 (f[u][d][l][r]) 为 ((u, l)) 到 ((d, r)) 这个矩形最小的复杂度是多少
那么转移就是
[f[u][d][l][r] =
egin{cases}
max(f[u][k][l][r], f[k + 1][d][l][r])+1, k in [u, d - 1]\
max(f[u][d][l][k], f[u][d][k + 1][r]) + 1, k in [l, r - 1]\
end{cases}
]
然而这是一个 (O(n^5)) 的 DP
发现答案最大不会超过 (log(n) + log(m))
考虑将复杂度这一维放到状态中来, 对状态中的某一维 DP
设 (f[c][u][d][l]) 为复杂度为 (c) 时, 矩形最上面一行在 (u), 最下面一行在 (d), 以 (l) 为最左边一列能够延伸多少列
发现竖着切是很简单的
[f[c][u][d][l] = f[c - 1][u][d][l] + f[c - 1][u][d][l + f[c - 1][u][d][l]]
]
横着切的较为复杂
[f[c][u][d][l] = max(min(f[c - 1][u][k][l], f[c - 1][k + 1][d][l])), k in [u, d - 1]
]
此时的复杂度是 (O(n^4log_n))的
考虑到随着 (k - u) 越来越大, (min) 式中左边的数单调不升, 右边的数单调不降
设 (min) 式中左边的数为 (a), 右边的数为 (b)
当 (a, b) 越来越接近的时候他们的 (min) 就会越大
那么我们只需要二分出最后一个使得 (a > b) 的 (k), 设其为 (k_1)
那么只需要将 (k_1) 和 (k_1 + 1)两个位置的取较大的那个即可
复杂度变为 (O(n^3log_n^2))
Code
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
const int N = 205;
using namespace std;
int n, m, sum[N][N], f[2][N][N][N], ans;
char s[N];
template < typename T >
inline T read()
{
T x = 0, w = 1; char c = getchar();
while(c < '0' || c > '9') { if(c == '-') w = -1; c = getchar(); }
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * w;
}
int calc(int u, int l, int d, int r)
{
if(r < l || d < u) return 0;
int res = (d - u + 1) * (r - l + 1), tmp;
tmp = sum[d][r] - sum[d][l - 1] - sum[u - 1][r] + sum[u - 1][l - 1];
return tmp == res || !tmp;
}
int binary(int opt, int u, int d, int k)
{
int l = u, r = d - 1, mid, tmp1, tmp2, res = 0;
if(l > r) return 0;
while(l <= r)
{
mid = (l + r) >> 1;
tmp1 = f[opt][u][mid][k], tmp2 = f[opt][mid + 1][d][k];
res = max(res, min(tmp1, tmp2));
if(tmp1 < tmp2) r = mid - 1;
else l = mid + 1;
}
return res;
}
void solve(int opt)
{
for(int tmp, u = 1; u <= n; u++)
for(int d = u; d <= n; d++)
for(int l = 1; l <= m; l++)
{
tmp = f[opt ^ 1][u][d][l], f[opt][u][d][l] = tmp + f[opt ^ 1][u][d][l + tmp];
f[opt][u][d][l] = max(f[opt][u][d][l], binary(opt ^ 1, u, d, l));
}
}
int main()
{
n = read <int> (), m = read <int> ();
for(int i = 1; i <= n; i++)
{
scanf("%s", s + 1);
for(int j = 1; j <= m; j++)
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + (s[j] == '.');
}
for(int lst = m, u = 1; u <= n; u++)
for(int d = u; d <= n; d++, lst = m)
for(int l = m; l; l--)
{
while(lst >= l && !calc(u, l, d, lst)) lst--;
f[0][u][d][l] = lst - l + 1;
}
for(int i = 1; f[(i & 1) ^ 1][1][n][1] < m; i++)
solve(i & 1), ans = i;
printf("%d
", ans);
return 0;
}
一点点小启示
当需要被 DP 的值很小的时候, 不妨将其提到状态中去, 对状态中的某一个值进行 DP