修补骑士一看见题面就放弃的差不多了。这里主要是绝大多数人可能会惯性思维的认为是从中间开始,之后往四边扩展验证,明显实现起来太过麻烦并且时间复杂度过大,不过考虑构造的思路是没有错的
对于每一个点,我们考虑积累三个元素:向上的延伸,向左上的延伸与向右上的延伸,对于一个方格(假设为右下角),我们先找到向上与向右最小的那一个,之后找到对应的右下角,之后就是右下角向上与向右最小的那一个,看能不能构建成一个封闭的蝴蝶形状。之所以能想到这个,是因为对于蝴蝶型就是由两个对角线与两条竖线决定的,我们只需要考虑到这四个元素就可以确定一个蝴蝶型(当然主要原因还是我看了题解.jpg)
思路有了大概的确定,我们打表又能发现两点:
1:只有一个X也算是蝴蝶型,并且是最小的蝴蝶型(顺带一提,这题目里的矩阵元素保证由X和O构成多半是假的,19号测试数据应该就是只有O,需要输出0)
2:蝴蝶型一定是个正方形,这主要是因为对角线X长度与边长X长度是一样导致的。同时必须要有中点也决定了这个正方形边长必须是奇数
修补骑士一开始来实现:
for(int r = 0;r < n;r++)
{
for(int p = 0;p < m;p++)
{
int downnum = 0;
while(downnum + r < n)
{
if(themap[downnum + r][p] == 1)
{
down[r][p]++;
}
else
{
break;
}
}
downnum = 0;
while(downnum + r < n && downnum + p < m)
{
if(themap[downnum + r][p + downnum] == 1)
{
rdown[r][p]++;
}
else
{
break;
}
}
while(downnum + r < n && p - downnum >= 0)
{
if(themap[downnum + r][p - downnum] == 1)
{
ldown[r][p]++;
}
else
{
break;
}
}
}
}
普普通通的扫描,可惜TLE了,这也是理所应当的,毕竟对于每个位次我们都进行了三次扫描操作。但是这实际上是可以优化的,也就是这里体现了动态规划思想
for(int r = 0;r < n;r++)
{
for(int p = 0;p < m;p++)
{
char t;
cin>>t;
if(t == 'X')
{
//单调方向怎么预处理一下呢,这下只有以下推上了
if(r - 1 >= 0) down[r][p] = down[r - 1][p] + 1;
else down[r][p] = 1;
if(r - 1 >= 0 && p - 1 >= 0) ldown[r][p] = ldown[r - 1][p - 1] + 1;
else ldown[r][p] = 1;
if(r - 1 >= 0 && p + 1 < m) rdown[r][p] = rdown[r - 1][p + 1] + 1;
else rdown[r][p] = 1;
}
else
{
down[r][p] = rdown[r][p] = ldown[r][p] = 0;
}
}
}
这里也就是为什么修补骑士使用了向上方有多少个X记录而不是向下走(都可以做出来),主要是方便我们使用DP,对于初始状态我们更好处理。将其结合读入过程就差不多了
这里最普遍也是我看题解与绝大多数别人AC代码都是预处理完成后扫描一次,对于每个位置,我们找到向上走与向右上走的min后定义k,k为偶数就减1(上面说明了必须是个奇数边长的正方形),考虑到形成的蝴蝶型不一定充分利用了所有的X(可能有多余的),我们需要写一个循环来寻找有没有更小但是可以组成蝴蝶的取值(k-=2即可),之后维护max即可
AC代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n,m;
cin>>n>>m;
vector<vector<int>> themap(n,vector<int>(m,0));
vector<vector<int>> down(n,vector<int>(m,0));
vector<vector<int>> ldown(n,vector<int>(m,0));
vector<vector<int>> rdown(n,vector<int>(m,0));
for(int r = 0;r < n;r++)
{
for(int p = 0;p < m;p++)
{
char t;
cin>>t;
if(t == 'X')
{
//单调方向怎么预处理一下呢,这下只有以下推上了
if(r - 1 >= 0) down[r][p] = down[r - 1][p] + 1;
else down[r][p] = 1;
if(r - 1 >= 0 && p - 1 >= 0) ldown[r][p] = ldown[r - 1][p - 1] + 1;
else ldown[r][p] = 1;
if(r - 1 >= 0 && p + 1 < m) rdown[r][p] = rdown[r - 1][p + 1] + 1;
else rdown[r][p] = 1;
}
else
{
down[r][p] = rdown[r][p] = ldown[r][p] = 0;
}
}
}
int maxk = 0;
for(int r = 0;r < n;r++)
{
for(int p = 0;p < m;p++)
{
int k = min(down[r][p],rdown[r][p]);
if(k % 2 == 0)
{
k--;//打表观察可得
}
for(int q = k;q > maxk;q -= 2)
//q > maxk是个很优秀但不是必要的剪枝,大概亏100ms
//所以说我们平常在循环找最大值的情况下,对于明确小于的枝条我们就可以剪掉!
//但感觉这个还是太吃熟练度了,唉,多练吧
{
int sk = min(down[r][p + q - 1],ldown[r][p + q - 1]);
if(sk < q)//这里也需要注意一下
{
continue;
}
else
{
maxk = q;
break;
}
}
}
}
cout<<maxk<<endl;
return 0;
}
这是绝大多数人的做法也是绝对正确的做法,不过修补骑士一开始想的方法不一样:
对于可能的k取值(最大就是min(n,m)因为切正方形),我们把k搞成奇数后来扫描验证,因为已经有了k我们可以很轻松的找到右下角的位置是p + k - 1(这里不是点而是方格,也要注意)并进行验证,并且不用上一个方法那样k-=2不停验证,但是实际上这个方法很明显捡了西瓜丢了芝麻,会多次扫描全图,而原本正确方法就只需要扫一次
但我还是把这个方法写出来了,应该是正确的,就是果然后面的测试数据TLE了。或者说这个方法还可以优化?希望有大佬前来分析指正
//这个是TLE的代码哈,上面那个才是AC的
#include<bits/stdc++.h>
#define int long long
using namespace std;
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n,m;
cin>>n>>m;
vector<vector<int>> themap(n,vector<int>(m,0));
vector<vector<int>> down(n,vector<int>(m,0));
vector<vector<int>> ldown(n,vector<int>(m,0));
vector<vector<int>> rdown(n,vector<int>(m,0));
for(int r = 0;r < n;r++)
{
for(int p = 0;p < m;p++)
{
char t;
cin>>t;
if(t == 'X')
{
themap[r][p] = 1;
if(r - 1 >= 0) down[r][p] = down[r - 1][p] + 1;
else down[r][p] = 1;
if(r - 1 >= 0 && p - 1 >= 0) ldown[r][p] = ldown[r - 1][p - 1] + 1;
else ldown[r][p] = 1;
if(r - 1 >= 0 && p + 1 < m) rdown[r][p] = rdown[r - 1][p + 1] + 1;
else rdown[r][p] = 1;
}
else
{
themap[r][p] = 0;
down[r][p] = rdown[r][p] = ldown[r][p] = 0;
}
}
}
int k = min(n,m);
if(k % 2 == 0)
{
k--;
}
for(;k > 0;k -= 2)
{
for(int r = 0;r < n;r++)
{
for(int p = 0;p < m;p++)
{
if(p + k - 1 < m)
{
if(themap[r][p + k - 1] == 0)
{
continue;
}
else
{
int min1 = min(down[r][p],rdown[r][p]);
int min2 = min(down[r][p + k - 1],ldown[r][p + k - 1]);
if(min(min1,min2) >= k)
{
cout<<k<<endl;
return 0;
}
else
{
continue;
}
}
}
else
{
continue;
}
}
}
}
cout<<0<<endl;
return 0;
}