• 洛谷 P1169 [ZJOI2007]棋盘制作


    题意

    给定一个n*m的01矩阵,寻找最大的,相邻值不相同的子矩阵与子方阵。

    (n,mleq 2,000)

    分析

    朴素的做法是枚举两个点来确定矩阵,再暴力判断能不能满足条件。

    复杂度(O(n^4))爆炸。考虑优化:判断能不能满足条件时,存在很多重复判断,可以预处理。

    但是,怎样进行预处理呢?这就需要我们改变枚举方式,简化预处理的步骤。

    悬线法

    今年应该不考

    是一种DP(?)算法,常用于求满足条件的矩阵。

    如果将一条线当做1*k的矩阵看待,则我们枚举每一条满足条件的线,并尝试将其向左右拓展构造矩阵。

    那么只要在左右拓展长度中分别取min,就是所求答案。这种方法的复杂度是多少?

    每个点如果和上方不同,就可以接到上面的线中。所以枚举每个点,就可以递推求出悬线up

    [up_{i,j}=up_{i-1,j}+(b_{i,j} ot= b_{i,j}) ]

    其中b是原始矩阵,初始值是1。复杂度(O(n^2))

    左右端拓展的对象是单个点,所以对于每个点都预处理出能向左右拓展到的位置。用递推求出。

    [egin{cases}b_{i,j} ot=b_{i,j-1}quad left_{i,j}=j\b_{i,j}=b_{i,j-1}quad left_{i,j}=left_{i,j-1}end{cases} ]

    [egin{cases}b_{i,j} ot=b_{i,j+1}quad right_{i,j}=j\b_{i,j}=b_{i,j+1}quad right_{i,j}=right_{i,j+1}end{cases} ]

    复杂度仍然(O(n^2))

    2k可过。

    正确性

    我们需要证明的是,悬线法可以枚举到所有矩形。

    假设我们要在这一团满足条件的联通块中找矩形。(丑,真TM丑)

    那么它会不会取蓝色呢?不会,很明显绿色比他更优。有一个性质:矩形边缘必然紧贴联通块边缘

    所以对于每条悬线,它取到的矩阵是尽量大的。

    (其实绿色也不满足,因为左右端没有取完整。)

    画图宝才,luogu捡到鬼了

    由刚才的性质得出,这个矩形所在的联通块即便边缘再粗糙,总是会有一条悬线的上端和矩形的上端重合。

    所以,只要每条悬线都枚举到,每个可能是答案的矩形就会枚举到,而且会让它们尽量大。这题就解决了。

    代码

    别看了 抄的

    #include<cstdio>//iostream库有left函数,会冲突
    #include<algorithm>
    using namespace std;
    
    const int MAXN=2005;
    int n,m;
    bool b[MAXN][MAXN];
    int left[MAXN][MAXN],right[MAXN][MAXN],up[MAXN][MAXN];
    int sq,rect,temp;
    
    int main()
    {
    	scanf("%d %d",&n,&m);
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++)
    			left[i][j]=right[i][j]=j,up[i][j]=1,scanf("%d",&b[i][j]);
    			//输入、初始化 
    	for(int i=1;i<=n;i++)
    		for(int j=2;j<=m;j++)
    			if(b[i][j]!=b[i][j-1])
    				left[i][j]=left[i][j-1];
    	for(int i=1;i<=n;i++)
    		for(int j=m-1;j>=1;j--)
    			if(b[i][j]!=b[i][j+1])
    				right[i][j]=right[i][j+1];
    			//递推预处理,注意范围和顺序 
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++)
    		{
    			if(i>1&&b[i-1][j]!=b[i][j])
    			{
    				up[i][j]=up[i-1][j]+1;
    				left[i][j]=max(left[i][j],left[i-1][j]);
    				right[i][j]=min(right[i][j],right[i-1][j]);
    				//由于是矩形,left和right不能越界,要取一遍最小值
    				//另外看清楚存储的是位置不是距离,所以左端取距离更短相当于位置更靠后,下标更大。 
    			}
    			temp=(right[i][j]-left[i][j]+1);
    			rect=max(rect,up[i][j]*temp);
    			sq=max(sq,min(up[i][j],temp)*min(up[i][j],temp));
    			//正方形同理。 
    		}
    	printf("%d
    %d
    ",sq,rect);
    	return 0;
    }
    

    最后,祝大家身体健康,再见。

  • 相关阅读:
    Java基础08 继承
    Java基础07 包
    Java基础06 组合
    Java基础05 实施接口
    Java基础04 封装与接口
    Java基础03 构造器与方法重载
    Java基础02 方法与数据成员
    Java基础01 从HelloWorld到面向对象
    151. Reverse Words in a String
    168. Excel Sheet Column Title
  • 原文地址:https://www.cnblogs.com/ehznehc/p/11438592.html
Copyright © 2020-2023  润新知