极其考思维的好题
题目背景
众所周知,$mathrm{Zdrcl}$是一名天天$mathrm{AK}$的高水平选手。
作为一民长者,为了向大家讲述自己$mathrm{AK}$的经验,他决定在一个礼堂里为大家举办一场演讲。题目描述
这个礼堂拥有$N imes M$个位子,排成$N$行$M$列。每个位子都有一盏灯,一开始有的灯是亮的,有的灯是灭的。这个礼堂十分诡异,人们操作一次只能使某一行或某一列(某一行或某一列由操作者你自己来决定)的灯的明暗状态全都发生转变(显然,我们不一定可以把所有的灯都点亮)。
来听演讲的人只会坐在灯已经被点亮的位置, 所以可以听演讲的位置只会是一个只由灯是亮的的位子所组成的矩形(不然坐太乱,$mathrm{Zdrcl}$会不高兴的) 。
$mathrm{Zdrcl}$知道会有很多人来听演讲, 所以他希望找到一个经过若干操作后的面积最大的只由灯是亮的的位子所组成的矩形。 这个任务当然由想$mathrm{AK}$的你来完成啦!
输入输出格式
输入格式:
第一行两个正整数表示$N,M$。
接下来有$N$行,每行有$M$个字符(‘#’表示这个灯初始状态是亮的,‘.’表示这个灯初始状态是暗的)。
输出格式:
一行一个整数表示你找到的矩形的面积。
输入输出样例
输入样例#1:
3 3 ..# ##. .#.输出样例#1:
6输入样例#2:
4 6 .#.... ...### .##.#. ...#..输出样例#2:
9说明
样例1解释
数据范围
对于$5\%$的数据:$N=2,M=2$
对于$15\%$的数据:$N imes Mle 8$
对于$30\%$的数据:$N,Mle 10$
对于$60\%$的数据:$Nle 1le 10^2$
对于$80\%$的数据:$Nle 4 imes 10^2$
对于$100\%$的数据:$Nle 2 imes 10^3$
输入文件比较大, 请使用比较快速的读入方法。
提示
这一题写起来不是很困难。
题解:
这个题只要第一步想对了,剩下的DP部分就不难写。主要就是考虑怎么判断是否能全部点亮。
多次手玩可以发现对于可以被全部点亮的部分的特性。因为异或的逆运算还是异或,灯的开关就是在异或。因此我们考虑把矩阵转化为0和1。
上面提到灯的开关是进行了异或,因此对于任意行或列的操作,它们的顺序是可以被打乱的。我们假设先进行列操作,我们要尽可能把序列变为形如
00000 11111 11111 00000
这样,才能在之后只进行行操作时可以完成矩阵的变换。
那么上面给出的矩阵原来可能是这样的
01001 10110 10110 01001
可以发现,它们左右异或的结果总是一列均为1或一列均为0。因为如果一列均为1的话,那么只需要对其中一列进行反转即可;如果一列均为0的话,这两列就是一样的,可以对它们进行相同的操作而达到目的。
1101 1101 1101 1101
不过这样异或会使得矩阵的宽度减小1,我们只需要在计算的时候把这一维加上1就可以了,因为有$latex x$个异或结果,代表了$latex x+1$列。最后再单独处理最优解只占一列的特殊情况。
而有了这样的矩阵,我们的目的就是求最大子矩阵,其中每一列的数字都相同。这样用单调栈可以做到$latex O(nm)$。
当时出题人给出的解法是通过前5%的数据找规律,发现对于一个合法的矩阵,它内部的每一块2×2区域都满足“两种状态出现的次数都是偶数”。 即0出现0次,1出现4次/0出现4次,1出现0次/0出现2次,1出现2次。这样的话感觉条件变少了,不过正确性也可以保证,暂时不写代码。(咕
Code:
#include<cstdio> #include<cstring> char c[2222][2222]; int Xor[2222][2222]; int con[2222]; int q[2222],l=0,r=0; int ext[2222]; int main() { int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n;++i) scanf("%s",c[i]+1); int ans=1,tt=1; for(int i=1;i<=n;++i) { tt=1; for(int j=1;j<=m;++j) if(c[i][j]==c[i][j-1]) { ++tt; ans=ans>tt?ans:tt; } else tt=1; } for(int i=1;i<=m;++i) { tt=1; for(int j=1;j<=n;++j) if(c[i][j]==c[i-1][j]) { ++tt; ans=ans>tt?ans:tt; } else tt=1; } for(int i=1;i<n;++i) for(int j=1;j<=m;++j) Xor[i][j]=(c[i][j]!=c[i+1][j]); for(int i=1;i<=m;++i) { l=0,r=0; for(int j=1;j<n;++j) { if(i==1||Xor[j][i]!=Xor[j][i-1]) con[j]=1; else con[j]++; int cnt=0; while(l<r&&con[j]<con[q[r]]) { int tmp=(j-q[r]+1+ext[r])*(con[q[r]]); ans=ans>tmp?ans:tmp; cnt+=ext[r]+1;//好久没用单调栈做题了,忘记还要加上之前延伸的块…… --r; } q[++r]=j; ext[r]=cnt; } for(int j=l+1;j<=r;++j) { int tmp=(n-q[j]+1+ext[j])*(con[q[j]]); ans=ans>tmp?ans:tmp; } } printf("%d ",ans); return 0; }