前言
前段时间放假就没怎么刷题了,今天开始继续刷题。先放题目链接:
P2241 统计方形(数据加强版)
我的解题思路:
本题被洛谷归在了暴力枚举门类下,作为小白,一开始我还是踩坑了的。开始我还真一个点一个点枚举过来然后算长方形和正方形数量,然后不出意外的TLE了。所以看似是暴力枚举,但是其实是有技巧在其中的。
我最早想到的技巧的动态规划。假设D1(u,v)表示长宽分别为u和v的矩形包含的正方形数量,那么求D1(u,v)可以通过D1(u,v)来求。理论上确实可行,但是这里n和m取值为1-5000,这样的话在n,m较大的时候很容易造成实际数量超过int范围而需要long int 甚至long long int(C99标准)。实际上在求长方形数量的时候确实会超int位长。这样记录正方形和长方形的数量的递推矩阵不可避免的要用long long int类型,这样的话又会造成MLE。
这样的话,仔细思考一番,利用动态规划的思路来做,其实不太需要把整个矩阵的存下来,通过D1(1,1)->D1(1,2)->…->D1(1,m)->D1(2,m)->D1(3,m)->…->D1(n,m)来求其实也可以,这样的话其实就是用一个变量来记录数量就行了。
因为不太好把草稿搬到上面来,我也不会画出那样的图,所以我就只能记录一下简要的递推式得出的思路:
(1)假设D1(u,v)为长宽为u和v的矩形包含的正方形数量。只考虑D1(u+1,v)和D1(u,v)的关系,这里,D1(u+1,v)比D1(u,v)多的是多出来一个1*v的矩形,因此,目前二者关系可以归结为D1(u+1,v)=D1(u,v)+add(1*v)。这个add(1*v)的表达式就是要找的。而1*v多出来的正方形数量,就是包含部分1*v内小正方形的所有正方形数量。这里,1*1的新增正方形数量为v,2*2的新增正方形数量为(v-1)…往后就要考虑v和u的大小关系了。当u<v的时候,正方形最大为u*u,这时候只能加到v-u个,因此add(1*v)此时就是v+(v-1)+…+(v-u)=
(
2
v
−
u
)
(
u
+
1
)
2
\frac{(2v-u)(u+1)}{2}
2(2v−u)(u+1);同时,若u
≥
\ge
≥v,那么加和就可以一直到1,则此时add(1*v)即为
v
(
v
+
1
)
2
\frac{v(v+1)}{2}
2v(v+1)。由于D1(u,v+1)和D1(u,v)的关系本质同山因此不讨论了。
(2)假设D2(u,v)为长宽为u和v的矩形包含的长方形数量(按照题目意思应该不认为正方形是特殊的长方形)。按照上述思路一样来讨论。这里依旧讨论D2(u+1,v)和D2(u,v)的关系。前者比后者多了一个1*v的小矩形。讨论新增长方形数量有点小复杂,先从新增小矩形导致的新增长方形长或者宽为1开始看,这样就有(u+1-1)*v=u*v个;为2则有(u+1-1)*(v-1)个…同理需要讨论u和v的大小关系,当u>v的时候,加和一直到(u+1-1)*1个,因此总加和为
u
v
(
v
+
1
)
2
\frac{uv(v+1)}{2}
2uv(v+1)反之,当新增长方形的长/宽超过u以后,则一直都是长方形,即为(u+1)*(v-u)+(u+1)*(v-u-1)+…+(u+1)*1,因此总加和是
(
2
v
−
u
+
1
)
u
2
2
+
(
v
−
u
+
1
)
(
v
−
u
)
2
\frac{(2v-u+1)u^2}{2}+\frac{(v-u+1)(v-u)}{2}
2(2v−u+1)u2+2(v−u+1)(v−u)。
利用上述递推关系去求D1(n,m)和D2(n,m)就可以了,具体方向如一开始说的那样。最后是我的AC代码:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main(int argc, char *argv[]) {
long long int n,m;
scanf("%lld%lld",&n,&m);
long long int i,j;
long long int matrix[n+1][m+1],matrix2[n+1][m+1];
long long int sum_squ=1,sum_rec=0;
for(j=2;j<=m;j++){
sum_squ=sum_squ+1;}
j=m;
for(i=2;i<=n;i++){
if(i-1>=j)
sum_squ=sum_squ+j*(1+j)/2;
else
sum_squ=sum_squ+(2*j-i+1)*i/2;
}
for(j=2;j<=m;j++){
sum_rec=sum_rec+j-1;
}
j=m;
for(i=2;i<=n;i++){
if(i-1<j){
sum_rec=sum_rec+(2*j-i+2)*(i-1)*(i-1)/2+i*(j-i+1)*(j-i+2)/2-(j-i+1);
}else{
sum_rec=sum_rec+j*(i-1)*(j+1)/2;
}
}
printf("%lld %lld",sum_squ,sum_rec);
return 0;
}