题面
在麦克雷的面前出现了一个有 n*m 个格子的矩阵,每个格子用“.”或“#”表示,“.”表示这个格子可以放东西,“#”则表示这个格子不能放东西。现在他拿着一条 1*2 大小的木棒,好奇的他想知道对于一些子矩阵,有多少种放木棒的方案。
输入文件名为 matrix.in。
第一行包含 2 个正整数 n,m。接下来 n 行每行包含 m 个字符“.”或“#”。第 n+1 行包含 1 个正整数 q,表示询问次数。
接下来 q 行每行包含 4 个正整数 r1,c1,r2,c2,分别表示询问的子矩阵的左上格子和右下格子的位置
30%: q < =100 100%: q <=1e5 , 1 <= n, m <=500
分析
刚看到1*2,我鼠躯一震,以为是那个二分图的板子,结果发现就一个木棒,qvq。。。
然后很明显是矩阵类dp啊,但是我好菜,想得好复杂!!!
类似于二维前缀和的思想,f[i][j]表示以(1,1)为左上角,(i,j)为右下角的矩阵里面的可以放木棒的方案数,所以只需要按照二维前缀和的计算方式就可以算出任意一个子矩阵的方案数。
但问题在于,可能会产生,被分离开的这两行或者两列中间有相邻的空白点,这样的情况是没有被算进去的,于是还要维护下面两个东西
h[i][j]表示第i行和i-1行之间在前j列相邻的空点的个数(即可放木棒方案数),l[j][i]表示第j列与j-1列之间在前i行相邻的空点个数(同理)
于是又是一个前缀和,又能O(1)扣除这个影响
现在考虑怎么N2推f[i][j]呢?
f[i][j]=f[i][j-1]+在i行时这j列中每相邻两列的可放木棒数量+第i行在j列为止的可放木棒数量
最后答案就是f[r2][c2]-f[r1-1][c2]-f[r2][c1-1]+f[r1-1][c1-1]-h[r1][c2]+h[r1][c1-1]-l[c1][r2]+l[c1][r1-1]
说实话,真的好容易写晕,写了2h,最后10min,才调出来。不得不说今天考试心态是真的好,明明是个口答题TAT,这一次和上一次考试都三分之二的时间都在调dp,说明我不仅dp是真的差,我还差得很执着2333
代码
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> using namespace std; #define N 505 int n,m,q,r1,c1,r2,c2,ans; char s[N]; int a[N][N],h[N][N],l[N][N],f[N][N]; inline void read(int &x) {x=0;char ch=getchar();while(ch<'0'||ch>'9')ch=getchar(); while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();} int main() { freopen("matrix.in","r",stdin); freopen("matrix.out","w",stdout); read(n);read(m); for(int i=1;i<=n;i++) { scanf("%s",s); for(int j=1;j<=m;j++)a[i][j]=(s[j-1]=='.'); } for(int i=2;i<=n;i++) { for(int j=1;j<=m;j++) { if(!h[i][j])h[i][j]=h[i][j-1]; if(a[i][j]&&a[i-1][j])h[i][j]++; } } for(int i=2;i<=m;i++) { for(int j=1;j<=n;j++) { if(!l[i][j])l[i][j]=l[i][j-1]; if(a[j][i]&&a[j][i-1])l[i][j]++; } } int tmp=0; for(int i=2;i<=m;i++)tmp+=l[i][1],f[1][i]+=tmp; for(int i=2;i<=n;i++) { int tmp=0; for(int j=1;j<=m;j++) { tmp+=l[j][i]-l[j][i-1]; f[i][j]=tmp+f[i-1][j]+h[i][j]; } } read(q); for(int i=1;i<=q;i++) { read(r1);read(c1);read(r2);read(c2); ans=f[r2][c2]-f[r1-1][c2]-f[r2][c1-1]+f[r1-1][c1-1]-h[r1][c2]+h[r1][c1-1]-l[c1][r2]+l[c1][r1-1]; printf("%d\n",ans); } return 0; }