题目:
长方形(rectangle.pas/c/cpp)
【题目描述】
小明今天突发奇想,想从一张用过的纸中剪出一个长方形。
为了简化问题,小明做如下的规定:
(1)这张纸的长度、宽度分别为n,m。小明将这张纸看成是由n*m个格子组成,在剪的时候,只能沿着格子的
边缘剪。
(2)这张纸有些地方小明以前在上面画过,剪出来的长方形不能含有以前画过的地方。
(3)剪出来的长方形的大小没有限制。
小明看着这张纸,想到了好多好多种剪的方法,可是到底有多少种呢?小明数不过来了,你能帮帮他吗?
【输入格式】
第一行两个正整数,n,m,表示这张纸的长度和宽度。
接下来有n行,每行m个字符,每个字符为‘*’或者‘.’。
字符‘*’表示以前在这个格子上画过,字符‘.’表示以前在这个格子上没有画过。
【输出格式】
仅一个整数,表示方案数。
【输入样例】
6 4
....
.***
.*..
.***
...*
.***
【输出样例】
38
【数据规模】
对于10%的数据,满足1<=n<=10,1<=m<=10
对于30%的数据,满足1<=n<=50,1<=m<=50
对于60%的数据,满足1<=n<=200,1<=m<=200
对于100%的数据,满足1<=n<=1000,1<=m<=1000
最后一个点所有的字符都为‘.’。
算法:枚举
首先说一下本题总共有四种方案。
解法一:
最朴素的穷举法。先穷举长方形的四条边,时间为O(n^4),然后再判定是否可放,时间为O(n^2),总的时间复杂度
为O(n^6),预计得分为10分。
解法二:(公式不是太理解)
预处理一个s数组,s[i,j]表示左上方i*j大小的格子中有多少个字符为'*'。那么如果要判断从第i行到第j行,从第
k列到第1列的长方形中有没有字符'*',就可以通过计算s[j,1]+s[i-1,k-1]-s[j,k-1]-s[i-1,1]而得到。如果这个值大
于0,那么说明不可以放,如果等于0,那么可以放。判断的时间被成功地降为O(1)。总的时间复杂度为O(n^4)。预计得
分为30。
解法三:
这个解法我感觉是思路最清楚的一种方法了,这个解法是在解法二的基础上继续改进的。先穷举长方形的上下两条
边,然后用解法二的方法,判断出在这两条边之间的每一列是否能放。然后从左到右扫一遍,对于某一段连续的能放的
列,如果有k列,那么有k*(k+1) shr 1种不同的放法。穷举的时间为O(n^2),统计的时间为O(n),总时间为O(n^3)。预
计得分为60。
解法四:
解法四用到了数据结构栈,这个本人不是特别理解,读者参见2006国家集训队论文《基本数据结构在信息学竞赛中
长方形(rectangle.pas/c/cpp)
【题目描述】
小明今天突发奇想,想从一张用过的纸中剪出一个长方形。
为了简化问题,小明做如下的规定:
(1)这张纸的长度、宽度分别为n,m。小明将这张纸看成是由n*m个格子组成,在剪的时候,只能沿着格子的
边缘剪。
(2)这张纸有些地方小明以前在上面画过,剪出来的长方形不能含有以前画过的地方。
(3)剪出来的长方形的大小没有限制。
小明看着这张纸,想到了好多好多种剪的方法,可是到底有多少种呢?小明数不过来了,你能帮帮他吗?
【输入格式】
第一行两个正整数,n,m,表示这张纸的长度和宽度。
接下来有n行,每行m个字符,每个字符为‘*’或者‘.’。
字符‘*’表示以前在这个格子上画过,字符‘.’表示以前在这个格子上没有画过。
【输出格式】
仅一个整数,表示方案数。
【输入样例】
6 4
....
.***
.*..
.***
...*
.***
【输出样例】
38
【数据规模】
对于10%的数据,满足1<=n<=10,1<=m<=10
对于30%的数据,满足1<=n<=50,1<=m<=50
对于60%的数据,满足1<=n<=200,1<=m<=200
对于100%的数据,满足1<=n<=1000,1<=m<=1000
最后一个点所有的字符都为‘.’。
算法:枚举
首先说一下本题总共有四种方案。
解法一:
最朴素的穷举法。先穷举长方形的四条边,时间为O(n^4),然后再判定是否可放,时间为O(n^2),总的时间复杂度
为O(n^6),预计得分为10分。
解法二:(公式不是太理解)
预处理一个s数组,s[i,j]表示左上方i*j大小的格子中有多少个字符为'*'。那么如果要判断从第i行到第j行,从第
k列到第1列的长方形中有没有字符'*',就可以通过计算s[j,1]+s[i-1,k-1]-s[j,k-1]-s[i-1,1]而得到。如果这个值大
于0,那么说明不可以放,如果等于0,那么可以放。判断的时间被成功地降为O(1)。总的时间复杂度为O(n^4)。预计得
分为30。
解法三:
这个解法我感觉是思路最清楚的一种方法了,这个解法是在解法二的基础上继续改进的。先穷举长方形的上下两条
边,然后用解法二的方法,判断出在这两条边之间的每一列是否能放。然后从左到右扫一遍,对于某一段连续的能放的
列,如果有k列,那么有k*(k+1) shr 1种不同的放法。穷举的时间为O(n^2),统计的时间为O(n),总时间为O(n^3)。预
计得分为60。
解法四:
解法四用到了数据结构栈,这个本人不是特别理解,读者参见2006国家集训队论文《基本数据结构在信息学竞赛中
的应用》,本解法预计得分为100。复杂度为O(n^2)。
program rectangle;
const
maxn=1000;
var
n,m:longint;
ans:int64;
s:array [0..maxn] of ansistring;
t:array [0..maxn,0..maxn] of longint;
procedure init;
var
i,j:longint;
begin
readln(n,m);
for i:=1 to n do readln(s[i]);
for i:=1 to n do
begin
for j:=1 to m do
begin
t[i,j]:=t[i-1,j];
if s[i,j]='*' then inc(t[i,j]);{用t[i,j]表示第i行前j列的*号的个数。}
end;
end;
end;
procedure main;
var
i,j,k,l:longint;
begin
for i:=1 to n do
begin
for j:=i to n do{枚举行。}
begin
k:=1;
while (k<=m) and (t[j,k]<>t[i-1,k]) do inc(k);{略过中间有障碍的列,非常巧妙。}
while k<=m do
begin
l:=k;
while (l<=m) and (t[j,l]=t[i-1,l]) do inc(l);{寻找无障碍的连续的行。}
ans:=ans+(l-k)*(l-k+1) shr 1;{利用了一下组合的相关知识,s=k*(k+1) shr 1,貌似很多找规律的题目中都
能见到这个式子。}
k:=l;
while (k<=m) and (t[j,k]<>t[i-1,k]) do inc(k);{跳过有障碍的列,再从下一个无障碍的列开始计数。}
end;
end;
end;
end;
begin
assign(input,'rectangle.in'); reset(input);
assign(output,'rectangle.out'); rewrite(output);
init;
main;
writeln(ans);
close(input); close(output);
end.