SSL 1633 农田个数 (数数版恶心DP)
题目描述:
你的老家在河北农村。过年时,你回老家去拜年。你家有一片
N
∗
M
N*M
N∗M农田,将其看成一个
N
∗
M
N*M
N∗M的方格矩阵,有些方格是一片水域。你的农村伯伯听说你是学计算机的,给你出了一道题: 他问你:这片农田总共包含了多少个不存在水域的正方形农田。
两个正方形农田不同必须至少包含下面的两个条件中的一条:
边长不相等
左上角的方格不是同一方格
输入格式:
输入数据第一行为两个由空格分开的正整数
N
、
M
N、M
N、M(1<=
m
m
m 第2行到第
N
+
1
N+1
N+1行每行有
M
M
M个数字(
0
0
0或
1
1
1),描述了这一片农田。
0
0
0表示这个方格为水域,否则为农田(注意:数字之间没有空格,而且每行不会出现空格)
输出格式
不存在水域的不同正方形农田个数。
输入样例:
3 3
110
110
000
输出样例:
5
解题思路:
方法一:
我们可以通过正方形农田的边长以及他的右下角坐标来描述这块农田,考虑DP。
设
f
i
,
j
f_{i,j}
fi,j 表示以
(
i
,
j
)
(i,j)
(i,j) 为右下角的最大正方形农田边长。显然,若
(
i
,
j
)
(i,j)
(i,j) 本身为水域,那么很显然无法构成任何以
(
i
,
j
)
(i,j)
(i,j) 作为右下角的无水正方形农田。因此易得若
(
i
,
j
)
(i,j)
(i,j) 为水域,则
f
i
,
j
=
0
f_{i,j}=0
fi,j=0。
若
(
i
,
j
)
(i,j)
(i,j) 为农田,那么经过一点实践就能推出,以
(
i
,
j
)
(i,j)
(i,j) 为右下角的正方形农田的最大边长为:
f
i
,
j
=
m
i
n
(
f
i
−
1
,
j
,
f
i
,
j
−
1
,
f
i
−
1
,
j
−
1
)
+
1
f_{i,j}=min(~f_{i-1,j},f_{i,j-1},f_{i-1,j-1}~)+1
fi,j=min( fi−1,j,fi,j−1,fi−1,j−1 )+1
不难发现,若要用
(
i
,
j
)
(i,j)
(i,j) 为右下角构成一个边长大于
1
1
1 的无水正方农田,显然必须要使
(
i
−
1
,
j
)
,
(
i
,
j
−
1
)
,
(
i
−
1
,
j
−
1
)
(i-1,j),(i,j-1),(i-1,j-1)
(i−1,j),(i,j−1),(i−1,j−1) 都为农田,否则就构不成一个正方形。
方程最后的
+
1
+1
+1 即表明
(
i
,
j
)
(i,j)
(i,j) 本身也是一个边长为
1
1
1 的无水正方形农田,与
m
i
n
(
f
i
−
1
,
j
,
f
i
,
j
−
1
,
f
i
−
1
,
j
−
1
)
min(~f_{i-1,j},f_{i,j-1},f_{i-1,j-1}~)
min( fi−1,j,fi,j−1,fi−1,j−1 ) 加上,即为最长边正方形。
因此
f
f
f 的动态转移方程即为:
f
i
,
j
=
{
0
m
a
p
i
,
j
=
0
m
i
n
(
f
i
−
1
,
j
,
f
i
,
j
−
1
,
f
i
−
1
,
j
−
1
)
+
1
m
a
p
i
,
j
=
1
f_{i,j}=\begin{cases} 0&map_{i,j}=0\\ min(~f_{i-1,j},f_{i,j-1},f_{i-1,j-1}~)+1&map_{i,j}=1 \end{cases}
fi,j={0min( fi−1,j,fi,j−1,fi−1,j−1 )+1mapi,j=0mapi,j=1
我们能够发现,若
f
i
,
j
f_{i,j}
fi,j 为以
(
i
,
j
)
(i,j)
(i,j) 为右下角的最大正方形边长,那么显然以
(
i
,
j
)
(i,j)
(i,j) 为右下角的所有正方形即为
f
i
,
j
f_{i,j}
fi,j 个,且他们在整张地图中都是题目中所描述的“互不相同”的
因此答案还可以这么来:
a
n
s
=
∑
i
=
1
n
∑
j
=
1
m
f
i
,
j
ans=\sum_{i=1}^{n} \sum_{j=1}^{m} f_{i,j}
ans=i=1∑nj=1∑mfi,j
#include <iostream>
using namespace std;
int n,m;
char map[1001][1001];
int f[1001][1001]={0},f1[1000010]={0};
long long sum[1000010]={0},ans=0;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>map[i][j];
map[i][j]-='0';
if(map[i][j]) f[i][j]=min(min(f[i-1][j],f[i][j-1]),f[i-1][j-1])+1;
else f[i][j]=0;
ans+=f[i][j];
}
}
cout<<ans;
return 0;
}
方法二:
设 f 1 k f1_{k} f1k,表示 所有的 f i , j f_{i,j} fi,j 值为 k k k,(即最大边长为 k k k 的农田) 的个数。
此时还需要一个
s
u
m
i
sum_i
sumi 表示在整张地图中,边长为
i
i
i 的所有农田的个数,那么此时易得每一个边长为
i
+
1
i+1
i+1 的正方形中都必定含有至少一个边长为
i
i
i 的正方形,并且有某些边长为
i
i
i 的正方形独立(注意,这里的独立既指周围都是水域的正方形,亦指在若右下角为
(
i
,
j
)
(i,j)
(i,j),那么边长等于
f
i
,
j
f_{i,j}
fi,j 的那些正方形,这种正方形往往还能存在于大正方形的左上角)存在,他们的个数即
f
1
i
f1_{i}
f1i。
因此可推知
s
u
m
i
=
s
u
m
i
+
1
+
f
1
i
sum_{i}=sum_{i+1}+f1_{i}
sumi=sumi+1+f1i
此时,只需要每局地图中可能的所有正方形的边长,然后将 s u m i sum_i sumi 累加,即为满足条件的正方形个数。
a n s = ∑ i = 1 n s u m i ans=\sum_{i=1}^{n} sum_i ans=i=1∑nsumi
#include <iostream>
using namespace std;
int n,m;
char map[1001][1001];
int f[1001][1001]={0},f1[1000010]={0};
long long sum[1000010]={0},ans=0;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>map[i][j];
map[i][j]-='0';
if(map[i][j]) f[i][j]=min(min(f[i-1][j],f[i][j-1]),f[i-1][j-1])+1;
else f[i][j]=0;
f1[f[i][j]]++;
}
}
for(int i=n;i>=1;i--)
sum[i]=sum[i+1]+f1[i],ans+=sum[i];
cout<<ans;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!