[题解] [AGC033D] Complexity
题面
题解
设 \(f[u][d][l][r]\) 为 \((u, l)\) 到 \((d, r)\) 这个矩形最小的复杂度是多少
那么转移就是
\[f[u][d][l][r] =
\begin{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\n", ans);
return 0;
}
一点点小启示
当需要被 DP 的值很小的时候, 不妨将其提到状态中去, 对状态中的某一个值进行 DP