题解 AGC033D【Complexity】

problem

定义一个 0/1 矩阵 \(B\) 的复杂度为:

  • \(B\) 只由一种数字组成,其复杂度为 \(0\)
  • 否则,用一条平行于矩阵 \(B\) 任意一边的直线将 \(B\) 划分为两部分,则复杂度为所有划分方案中 两个子矩阵的复杂度的最大值加一 的最小值。

求出一个 \(n\times m\) 的 0/1 矩阵 \(A\) 的复杂度。\(n,m\leq 200\)

solution 0

naive 的 DP:令 \(f(l,r,d,u)\) 表示 \(A\) 的一个子矩阵取 \(i\in[l,r],j\in[d,u]\) 的部分的复杂度。转移枚举直线,\(O(n^5)\)

solution 1

考虑:

  • 答案的下界:每次对半分矩阵,形成一棵满二叉树,深度 \(\log n+\log m=\log nm\)
  • \(f\) 随便拎出一维,都有单调性,显然的。

\(f(ans,l,r,d)\) 表示,当我们固定了答案为 \(ans\),已知 \([l,r],d\) 求最大的 \(u\)(有 LGP3147 内味了)。

对半分转移:假如我们求出的 \(u\) 给它加一,则 \(f_{ans,l,r,d}=f_{ans-1,l,r,f_{ans-1,l,r,d}}\),细节平凡,\(ans=0\) 显然随意二分。

中间劈开两半的转移,可以枚举中间点,变成两条曲线的 \(\max\),目测可以随意二分,这时复杂度是 \(O(n^3\log^2 n)\)

solution 2

我们再开一个 \(g_{ans,d,u,l}\),和 \(f\) 差不多,方向反过来。对半分的转移一样。

对于中间劈开两半的转移,你固定 \(l,d\),相当于一个 \(g_{d,u,l}\) 可以对所有 \(f_{l,[l\leq r\leq g_{d,u,l}],d}\) 作贡献,反过来也是。

从小到大枚举 \(u\),由于 \(g_{d,u,l}\) 单调,相当于每个决策更新的状态是一段平摊线性的区间,直接扫即可。\(O(n^3\log n)\)

code

建议手推一遍,意会一下。

需要对 \(ans\) 滚动数组。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
//#undef LOCAL
#ifdef LOCAL
#define debug(...) fprintf(stderr,##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
typedef long long LL;
int n,m,a[310][310],f[2][310][310][310],g[2][310][310][310],crx;
bool check(int l,int r,int d,int u){int s=a[r][u]-a[l-1][u]-a[r][d-1]+a[l-1][d-1]; return !s||s==(r-l+1)*(u-d+1);}
int A[310],B[310];
int dp(){
	//f[0],g[0]
	for(int l=1;l<=n;l++)
		for(int r=l;r<=n;r++){
			f[0][l][r][m+1]=f[1][l][r][m+1]=m+1;
			for(int d=1;d<=m;d++){
				int&res=f[crx][l][r][d]=d;
				for(int L=d,R=m,mid=(L+R)>>1;L<=R;mid=(L+R)>>1){
					if(check(l,r,d,mid)) res=mid+1,L=mid+1;
					else R=mid-1;
				}
			}
		}
	for(int d=1;d<=m;d++)
		for(int u=d;u<=m;u++){
			g[0][d][u][n+1]=g[1][d][u][n+1]=n+1;
			for(int l=1;l<=n;l++){
				int&res=g[crx][d][u][l]=l;
				for(int L=l,R=n,mid=(L+R)>>1;L<=R;mid=(L+R)>>1){
					if(check(l,mid,d,u)) res=mid+1,L=mid+1;
					else R=mid-1;
				}
			}
		}
	if(f[crx][1][n][1]>m) return 0;
	crx^=1;
	//f[k],g[k]
	for(int k=1;k<=17;k++,crx^=1){
		for(int l=1;l<=n;l++)
			for(int r=l;r<=n;r++)
				for(int d=1;d<=m;d++)
					f[crx][l][r][d]=f[crx^1][l][r][f[crx^1][l][r][d]];
		for(int d=1;d<=m;d++)
			for(int u=d;u<=m;u++)
				for(int l=1;l<=n;l++)
					g[crx][d][u][l]=g[crx^1][d][u][g[crx^1][d][u][l]];
		for(int l=1;l<=n;l++)
			for(int d=1;d<=m;d++){
				for(int r=l;r<=n;r++) A[r]=f[crx][l][r][d];
				for(int u=d;u<=m;u++) B[u]=g[crx][d][u][l];
				A[n+1]=d,B[m+1]=l;
				for(int r=l;r<=n;r++){
					for(int u=A[r]-1;u>=A[r+1];u--) g[crx][d][u][l]=max(g[crx][d][u][l],r+1),debug("trans: g[%d][%d][%d]<=f[%d][%d][%d]\n",d,u,l,l,r,d);
					//for(int u=A[r]-1;u>=l;u--) 是暴力
				}
				for(int u=d;u<=m;u++){
					for(int r=B[u]-1;r>=B[u+1];r--) f[crx][l][r][d]=max(f[crx][l][r][d],u+1),debug("trans: f[%d][%d][%d]<=g[%d][%d][%d]\n",l,r,d,d,u,l);
				}
			}
		if(f[crx][1][n][1]>m) return k;
	}
	return -1;
}
int main(){
	#ifndef LOCAL
		freopen("painting.out","w",stdout);
	 	freopen("painting.in","r",stdin);
	#endif
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		char s[1010]; scanf("%s",s+1);
		for(int j=1;j<=m;j++) a[i][j]=s[j]=='.';
		for(int j=1;j<=m;j++) a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
	}
	printf("%d\n",dp());
	return 0;
}
posted @ 2022-11-09 22:54  caijianhong  阅读(48)  评论(0编辑  收藏  举报