Codeforces 373D Counting Rectangles is Fun【Dp】

本文介绍了一种算法,用于解决在一个N*M的矩阵中快速查询特定区间内全由0组成的子矩阵的数量问题。通过预处理的方式实现了高效查询。

D. Counting Rectangles is Fun
time limit per test
4 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

There is an n × m rectangular grid, each cell of the grid contains a single integer: zero or one. Let's call the cell on the i-th row and the j-th column as (i, j).

Let's define a "rectangle" as four integers a, b, c, d (1 ≤ a ≤ c ≤ n; 1 ≤ b ≤ d ≤ m). Rectangle denotes a set of cells of the grid{(x, y) :  a ≤ x ≤ c, b ≤ y ≤ d}. Let's define a "good rectangle" as a rectangle that includes only the cells with zeros.

You should answer the following q queries: calculate the number of good rectangles all of which cells are in the given rectangle.

Input

There are three integers in the first line: nm and q (1 ≤ n, m ≤ 40, 1 ≤ q ≤ 3·105). Each of the next n lines contains m characters — the grid. Consider grid rows are numbered from top to bottom, and grid columns are numbered from left to right. Both columns and rows are numbered starting from 1.

Each of the next q lines contains a query — four integers that describe the current rectangle, abcd (1 ≤ a ≤ c ≤ n; 1 ≤ b ≤ d ≤ m).

Output

For each query output an answer — a single integer in a separate line.

Examples
input
5 5 5
00101
00000
00001
01000
00001
1 2 2 4
4 5 4 5
1 2 5 2
2 2 4 5
4 2 5 3
output
10
1
7
34
5
input
4 7 5
0000100
0000010
0011000
0000000
1 7 2 7
3 1 3 1
2 3 4 5
1 2 2 7
2 2 4 7
output
3
1
16
27
52
Note

For the first example, there is a 5 × 5 rectangular grid, and the first, the second, and the third queries are represented in the following image.

  • For the first query, there are 10 good rectangles, five 1 × 1, two 2 × 1, two 1 × 2, and one 1 × 3.
  • For the second query, there is only one 1 × 1 good rectangle.
  • For the third query, there are 7 good rectangles, four 1 × 1, two 2 × 1, and one 3 × 1.

题目大意:


给出一个N*M的矩阵,有Q个查询,每个查询表示询问区间内有多少个全0子矩阵。


思路:


设定Dp【i】【j】【k】【l】表示区间(【a,b】【c,d】)内全0子矩阵的个数。

那么状态转移方程有:

Dp【i】【j】【k】【l】=Dp【i】【j】【k-1】【l】+Dp【i】【j】【k】【l-1】-Dp【i】【j】【k-1】【l-1】+以点(k,l)结尾能够组成的全0子矩阵的个数。


那么O(n^2*m^2)预处理然后O(1)查询答案即可。


Ac代码:

#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
int row[55][55];
char a[55][55];
int dp[55][55][55][55];
int main()
{
    int n,m,q;
    while(~scanf("%d%d%d",&n,&m,&q))
    {
        for(int i=1;i<=n;i++)
        {
            scanf("%s",a[i]+1);
        }
        memset(dp,0,sizeof(dp));
        memset(row,0,sizeof(row));
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                if(a[i][j]=='0')
                row[i][j]=row[i][j-1]+1;
                else row[i][j]=0;
            }
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                for(int k=i;k<=n;k++)
                {
                    for(int l=j;l<=m;l++)
                    {
                        dp[i][j][k][l]=dp[i][j][k][l-1]+dp[i][j][k-1][l]-dp[i][j][k-1][l-1];
                        int r=l-j+1;
                        for(int z=k;z>=i;z--)
                        {
                            r=min(r,row[z][l]);
                            dp[i][j][k][l]+=r;
                        }
                    }
                }
            }
        }
        while(q--)
        {
            int a,b,c,d;
            scanf("%d%d%d%d",&a,&b,&c,&d);
            printf("%d\n",dp[a][b][c][d]);
        }
    }
}











Codeforces Round 1000 Div. 2 F1 题目 "Counting Is Not Fun (Easy Version)" 中,问题的核心是计算满足特定条件的子数组数量,而这些子数组的元素值与它们在子数组中的位置相关。 ### 题目简述 题目要求找到数组中满足特定条件的子数组数量。具体而言,对于一个长度为 $ n $ 的数组 $ a $,要求统计所有子数组 $ [l, r] $(其中 $ 1 \leq l \leq r \leq n $)的数量,使得对于该子数组内的每个位置 $ i $(相对于子数组的起始点 $ l $ 的偏移),满足 $ a[i] = i - l $。 ### 解题思路 1. **滑动窗口方法**:可以使用滑动窗口的思想来解决这个问题。我们遍历数组,尝试找到尽可能长的连续段,其中每个元素的值等于其相对于当前段起始位置的偏移量。对于每一个起始位置 $ l $,找到最大的 $ r $,使得所有 $ a[i] = i - l $ 对于 $ i \in [l, r] $ 成立。 2. **统计符合条件的子数组数量**:对于每一个满足条件的连续段,长度为 $ len $,则该段内可以形成的子数组数目为 $ len \times (len + 1) / 2 $。这是因为长度为 $ len $ 的连续段可以形成 $ len + (len - 1) + ... + 1 = len \times (len + 1) / 2 $ 个子数组。 3. **遍历数组**:通过遍历数组并维护当前段的起始位置,可以高效地找到所有符合条件的连续段并统计其对应的子数组数量。 ### 时间复杂度 该算法的时间复杂度为 $ O(n) $,因为每个元素最多被访问一次,且没有嵌套循环。 ### 示例代码 以下是一个实现该思路的 Python 示例代码: ```python n = int(input()) a = list(map(int, input().split())) count = 0 i = 0 while i < n: if a[i] != 0: i += 1 continue # 找到最长的连续段 j = i while j < n and a[j] == j - i: j += 1 length = j - i count += length * (length + 1) // 2 i = j print(count) ``` ### 代码说明 1. 首先读取输入的数组。 2. 遍历数组,寻找所有起始位置 $ i $,其中 $ a[i] = 0 $,因为这是可能的子数组起点。 3. 对于每一个起点 $ i $,找到最长的连续段 $ [i, j) $,使得所有元素满足 $ a[k] = k - i $。 4. 根据连续段的长度计算符合条件的子数组数量,并累加到最终结果中。 ### 相关优化 在实现中,需要注意避免重复计算或遗漏某些情况,例如当数组中存在多个连续段时,确保每个段都被正确分割并计算。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值