传送门:数三角形
标签:枚举
题目大意
给出一个只由“ * ”和“ . ”组成的n x m的字符矩阵,现在规定其中高为h且顶点坐标为(x,y)的三角形满足以下条件:1、坐标为(x,y),(x+1,y-1),(x+2,y-2)……(x+h-1,y-h+1)的字符都为星号,2、坐标为(x,y),(x+1,y+1),(x+2,y+2)……(x+h-1,y+h-1)的字符都为“ . ”,3、三角形底边所有字符全为星号。另外,同一个星号可以被多个三角形包括,也就是说一个高为3的大三角形包括两个高为2的小三角形。求给出的字符矩阵中有多少个三角形?
输入:第一行两个正整数n,m(1<=n,m<=500),分别代表字符矩阵的行数和列数。接下来n行每行m个字符,代表字符矩阵。
输出:一个正整数,代表字符矩阵中三角形的数量。
算法分析
- 题目说一个高为3的大三角形包括两个高为2的小三角形,那我们自然会想到先统计大三角形的数量,再逐次减小高度统计小三角形的数量。但这题显然没那么难,观察数据范围我们就会发现,n和m都不超过500,500的三次方只有1e8,也就是说时间复杂度为O(n3)的做法是不会超时的,那我们就可以直接枚举。
- 枚举什么才能做到不重不漏呢?那当然是顶点。对于一个点,将其斜边向左右两边扩展,遇到不为星号的点则停止,问题就在于每次扩展时如何O(1)判断底边是否合法。我们自然而然会想到前缀和,只要提前预处理出横向的前缀和,就能确定同一行中某段区间中的字符是否全为星号,也就知道底边是否合法。
- 当然,也可以先枚举高度,再枚举顶点坐标。只不过这种方法需要用到斜边前缀和,也就是在正对角线和副对角线的方向各做一次前缀和,取差分的时候则与普通前缀和一致。值得注意的是,先枚举顶点再利用斜边前缀和进行二分是错误的做法,因为二分的过程中无法限制底边。所以还是要用枚举的做法。若将m看作与n相等,则其时间复杂度是O(n3)。
代码实现
#include <iostream>
using namespace std;
char g[1001][1001];
long long sum[3][1001][1001];
#define lowbit(x) (x)&(-x)
int main(){
long long dx,dy,p,q,i,j,x,y,x1,y1,n,m,T,mx,ans,k;
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
T=1;
while(T--){
cin>>n>>m;
ans=0LL;
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
cin>>g[i][j];
for(i=1;i<=n;i++)
for(j=m;j>=1;j--)
sum[0][i][j]=sum[0][i-1][j+1]+(g[i][j]=='*');
for(i=1;i<=n;i++)
for(j=1;j<=m;j++){
sum[1][i][j]=sum[1][i-1][j-1]+(g[i][j]=='*');
sum[2][i][j]=sum[2][i][j-1]+(g[i][j]=='*');
}
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
if(g[i][j]=='*')
for(k=1;k<j;k++)
if(sum[0][i+k][j-k]-sum[0][i][j]==k&&sum[1][i+k][j+k]-sum[1][i][j]==k&&sum[2][i+k][j+k]-sum[2][i+k][j-k-1]==2*k+1)
ans++;
cout<<ans;
}
return 0;
}