[题解] [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

posted @ 2020-04-28 20:00  ztlztl  阅读(187)  评论(0编辑  收藏  举报