题解 P1736 【创意吃鱼法】 暴力枚举做法
wjyyy
2018-04-01 00:04:37
首先,这题和[P1387 最大正方形](https://www.luogu.org/problemnew/show/P1387)很像,主体思路和[WYS大佬的暴力方向](https://www.luogu.org/blog/WYW-wys/solution-p1387)差不多
我们可以用前缀和处理出各个二维子矩阵的大小,尤其是正方形。
这里二维前缀和用到了容斥原理,相信学过集合的高中同学们应该掌握的差不多了
那么这样可以处理掉在斜率为$-1$的直线上的“鱼”了,我们还需要处理斜率为$1$的直线上的鱼,就可以将矩阵对称过来,用这样一句话:
```cpp
c[i][j]=a[i][m-j+1];
```
就可以重复做一遍枚举了
最根本的想法是:在一个$n*n$的正方形中,前缀和是$n$,则这个正方形可以被“创意地”吃掉。因此,我们需要向右下角遍历,但这样会平添一些不必要的步骤,所以我们借助DP思想,承接左上角的状态,向下遍历,于是就有了这份**~~90分~~**代码
```cpp
#include<cstdio>
#include<cstring>
int a[2501][2501];//正向矩阵
int b[2501][2501];//正向前缀和
int f[2501][2501];//dp用数组
int c[2501][2501];//镜像矩阵
int d[2501][2501];//镜像前缀和
int turn1(int x1,int y1,int x2,int y2)//处理二维前缀和
{
return b[x2][y2]-b[x1-1][y2]-b[x2][y1-1]+b[x1-1][y1-1];
}
int turn2(int x1,int y1,int x2,int y2)
{
return d[x2][y2]-d[x1-1][y2]-d[x2][y1-1]+d[x1-1][y1-1];
}
int main()
{
memset(b,0,sizeof(b));
memset(f,0,sizeof(f));
int n,m,maxx=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%d",&a[i][j]);
b[i][j]=b[i-1][j]+b[i][j-1]-b[i-1][j-1]+a[i][j];
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
c[i][j]=a[i][m-j+1];
d[i][j]=d[i-1][j]+d[i][j-1]-d[i-1][j-1]+c[i][j];
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(a[i][j]==1&&turn1(i-f[i-1][j-1],j-f[i-1][j-1],i,j)==f[i-1][j-1]+1)
//问题出现在这里,如果半途终止,那么也不能用到之前的状态,但实际上是有之前的状态的,见hack数据
f[i][j]=f[i-1][j-1]+1;
if(f[i][j]>maxx)
maxx=f[i][j];
}
memset(f,0,sizeof(f));
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(c[i][j]==1&&turn2(i-f[i-1][j-1],j-f[i-1][j-1],i,j)==f[i-1][j-1]+1)
//同上
f[i][j]=f[i-1][j-1]+1;
if(f[i][j]>maxx)
maxx=f[i][j];
}
printf("%d\n",maxx);
return 0;
}
```
```
8 8
1 0 1 0 0 0 0 0
0 1 0 0 0 0 0 0
1 0 1 0 0 0 0 0
0 0 0 1 0 0 0 0
0 0 0 0 1 0 0 0
0 0 0 0 0 1 0 0
0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 1
```
它为什么90分呢,是它可以被这样的一组数据hack掉,这个程序在承接不了左上角的最大DP正方形边长$a$时,会自动退回$1$,而不是从$a$到$1$之间找最大的合适的正方形,这样一来,在图中$(3,3)$点时,$f[3][3]$就被重置为零,因此解法有问题 _这里好像可以二分,~~但是感觉优化不大,毕竟常数是种信仰~~,最好还是优化一下,有$2500$的数据呢,~~虽然我没有这样做~~_
那么下面贴$AC$代码
```cpp
#include<cstdio>
#include<cstring>
int a[2501][2501];//正向矩阵
int b[2501][2501];//正向前缀和
int f[2501][2501];//dp用数组
int c[2501][2501];//镜像矩阵
int d[2501][2501];//镜像前缀和
int turn1(int x1,int y1,int x2,int y2)//二位前缀和处理
{
return b[x2][y2]-b[x1-1][y2]-b[x2][y1-1]+b[x1-1][y1-1];
}
int turn2(int x1,int y1,int x2,int y2)
{
return d[x2][y2]-d[x1-1][y2]-d[x2][y1-1]+d[x1-1][y1-1];
}
int main()
{
memset(b,0,sizeof(b));
memset(f,0,sizeof(f));
int n,m,maxx=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%d",&a[i][j]);
b[i][j]=b[i-1][j]+b[i][j-1]-b[i-1][j-1]+a[i][j];
}
for(int i=1;i<=n;i++)//处理镜像矩阵
for(int j=1;j<=m;j++)
{
c[i][j]=a[i][m-j+1];
d[i][j]=d[i-1][j]+d[i][j-1]-d[i-1][j-1]+c[i][j];
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)//正向枚举
{
if(a[i][j]==1)
{
f[i][j]=f[i-1][j-1]+1;//这里是可能出现的最大值
while(turn1(i-f[i][j]+1,j-f[i][j]+1,i,j)!=f[i][j])//判断此时的值能否成立
f[i][j]--;//此处是可以二分的
}
if(f[i][j]>maxx)
maxx=f[i][j];
}
memset(f,0,sizeof(f));
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)//镜像枚举(相当于已经处理过)
{
if(c[i][j]==1)
{
f[i][j]=f[i-1][j-1]+1;
while(turn2(i-f[i][j]+1,j-f[i][j]+1,i,j)!=f[i][j])
f[i][j]--;
}
if(f[i][j]>maxx)
maxx=f[i][j];
}
printf("%d\n",maxx);
return 0;
}
```