0%

The 2025 ICPC 德国 German Collegiate Programming Contest——F. Fair and Square

题目大意

题目背景
Felix 的生日派对上,一张 H×WH \times W 的矩形披萨被客人吃掉了一些部分,剩下的部分由若干 1×11 \times 1 的正方形单元格组成。

题目描述
给出一个 H×WH \times W 的网格,其中字符 # 表示剩下的披萨块,. 表示已经被吃掉(空缺)的部分。
你需要将所有剩下的披萨块(#)划分成若干个边长为 kk 的正方形区域。
要求:

  1. 每个划分出的正方形必须完全由 # 组成。

  2. 所有 # 必须被恰好覆盖一次(不能重叠,不能遗漏)。

  3. 只能沿网格线切割。

  4. 划分出的所有正方形边长必须相等。

求满足上述条件的最大边长 kk

输入格式
第一行包含两个整数 HHWW (1H,W25001 \le H, W \le 2500),表示网格的高度和宽度。
接下来 HH 行,每行包含一个长度为 WW 的字符串,由 #. 组成。
保证输入中至少包含一个 #

输出格式
输出一个整数,表示最大的可行边长 kk

样例 1

1
2
3
4
5
6
7
8
9
Input
4 7
####...
####.##
.######
.####..

Output
2

样例 1 解释
对于第一个样例,最大边长为 2。
剩下的披萨块可以被划分为 5 个 2×22 \times 2 的正方形,其左上角坐标 (r,c)(r, c) 分别为:(0,0),(0,2),(1,5),(2,1),(2,3)(0,0), (0,2), (1,5), (2,1), (2,3)
所有 # 均被这 5 个正方形不重不漏地覆盖。

image

这道题说白了其实意思很简单,就是井号的地方代表就是披萨,然后你要把披萨切成正方形的,大小相同的块,问这些切出来的正方形块的最大边长是多少?

样例 2

1
2
3
4
5
6
7
8
Input
3 5
#####
#####
###..

Output
1

样例 3

1
2
3
4
5
6
7
8
Input
3 5
.##..
#####
##.##

Output
1

样例 4

1
2
3
4
5
6
7
8
9
10
Input
5 13
....###......
....###..###.
.######..###.
.###.....###.
.###.........

Output
3

思路讲解

首先呢我们还是要想到啊,就是我我们首先还是要想到一个算法,能够判定在此边长下能否切割出这样子的东西啊。

这个算法的时间复杂度就是 O(H×W)O (H \times W) 就行,可以了。

这算法其实很简单,也不需要前缀和啊什么之类的。其实只需要一个贪心即可。就说白了,最上面的那个点,最上面最左边的那个点,或者你倒过来做最最左边最上面的那个点。你不放一个正方形在上面,就不以这个正方形,不放一个正方形在这里的话,没有正方形可以覆盖这个点所以这里必须放一个正方形。就这个披萨点。必须放一个正方形。那么你放了这,放下去这个正正方形以后,你就判断一下这个正方形会不会和其他你放的正方形重叠,啊什么之类的就行了。说白了就是一个贪心。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
auto check=[&](ll len) -> bool {
++tim;
for (ll i=1;i<=N;++i) {
for (ll j=1;j<=M;++j) {
if (vis[i][j]>=tim) continue;
if (maze[i][j]=='#') {
if (i+len-1>N) {
return false;
}
if (j+len-1>M) {
return false;
}
for (ll x=i;x<i+len;++x) {
for (ll y=j;y<j+len;++y) {
if (maze[x][y]!='#') {
return false;
}
if (vis[x][y]>=tim) {
return false;
}
vis[x][y]=tim;
}
}
}
}
}
return true;
};

现在问题就来到什么呢?就是我不可能枚举2500次是吧?枚举2500次就超了。那我怎么样枚举的次数少一点?我其实就加一个非常简单的一个判定即可。它 cnt 的因数数量肯定是根号下 cnt,也就是数量级是2500。又因为 i 乘 i 才是 才能组成 cnt 的一个因数,所以实际上 i 只有50的量级。

1
2
3
4
5
6
7
8
9
10
for (ll i=min(N,M);i>=2;--i) {
if (cnt%(i*i)!=0) {
continue;
}
if (check(i)) {
cout<<i<<"\n";
return;
}
}
cout<<1<<"\n";

AC代码

AC
https://codeforces.com/gym/106129/submission/362620886

心路历程(WA,TLE,MLE……)

WA

https://qoj.ac/submission/2030500

上面这个代码的问题是出在哪里呢?首先第一啊,就是这个因数啊,你直接乘起来,它去找这个最大,其实是不大对的。那么在 m 比较小的情况下,不如直接倒序遍历 m ,然后检验这个 m 的所有的素因数。是否全部都在我们可以凑出来的素因数集合之中。

不过后面我们发现更加大的问题,就是它这个道题目,它其实并不符合说有这个因数,然后我们乘,然后它就能够合在一起,即只要素因数合法,那么所凑出来的合数也合法,这其实是不对的,它们乘起来的合数是不一定合法的。其实很简单吧,就是边长为2和边长为3是没有反例的(即满足边长为2和满足边长为3,在不限制这个长度的情况下,应该是有满足边长为6的解决方案)。这个状况,让我们造成了一些错觉,以为说这个其实是可以乘的。但实际上不是这样子的。**满足边长为2,那一定满足边长为4吗?**一定满足边长为8吗?你看它这个2,它可以乘,它,但是它乘上去它就不一定对了。