AcWing
  • 首页
  • 课程
  • 题库
  • 更多
    • 竞赛
    • 题解
    • 分享
    • 问答
    • 应用
    • 校园
  • 关闭
    历史记录
    清除记录
    猜你想搜
    AcWing热点
  • App
  • 登录/注册

P8783 [蓝桥杯 2022 省 B] 统计子矩阵(前缀和+双指针滑动窗口)

作者: 作者的头像   DKing2917 ,  2025-06-11 18:10:36 · 天津 ,  所有人可见 ,  阅读 5


0


题目

https://www.luogu.com.cn/problem/P8783

思路

首先很容易想到的就是使用前缀和计算子矩阵的元素和。之后可以用暴力四重循环拿到80%的分数。

可以通过双指针“滑动窗口”优化一下。首先使用二重循环枚举子矩阵的上下边界,对于每一个确定上下边界的子矩阵,接下来要做的就是确定子矩阵的左右边界。

那么接下来的问题就由二维转换为了一维,和一维的滑动窗口具有异曲同工之妙。
使用双指针维护子矩阵的左右边界。按照习惯,使用外循环来维护滑动窗口的右端点(在这里是子矩阵的右边界),负责窗口的扩张;
然后左端点按照要求进行缩减窗口,维护一个边界状态。

对于一个确定右边界的滑动窗口,其左边界在窗口内均满足条件。所以加上贡献t-s+1即可。

Code

暴力80分代码

#include<iostream>
#include<cstdio>
using namespace std;

const int N = 510;
int n,m,k;
int a[N][N];

int Get(int x1,int y1,int x2, int y2)
{
    return a[x2][y2]-a[x2][y1-1]-a[x1-1][y2]+a[x1-1][y1-1];
}

int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= m; j ++)
            scanf("%d",&a[i][j]);

    //初始化前缀和
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= m; j ++)
            a[i][j] = a[i-1][j]+a[i][j-1]+a[i][j]-a[i-1][j-1];

    int res = 0;
    //开始枚举
    for(int x1 = 1; x1 <= n; x1 ++)
        for(int y1 = 1; y1 <= m; y1 ++)
            for(int x2 = x1; x2 <= n; x2 ++)
                for(int y2 = y1; y2 <= m; y2 ++)
                {
                    int sum = Get(x1,y1,x2,y2);
                    if(sum <= k)
                    {
                        res ++;
//                      printf("%d %d %d %d\n",x1,y1,x2,y2);
                    }
                    else
                        break;
                }
    printf("%d",res);
    return 0;
}

滑动窗口优化

#include<iostream>
#include<cstdio>
using namespace std;

typedef long long ll;
const int N = 510;
int n,m,k;
int a[N][N];

int Get(int x1,int y1,int x2, int y2)
{
    return a[x2][y2]-a[x2][y1-1]-a[x1-1][y2]+a[x1-1][y1-1];
}

int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= m; j ++)
            scanf("%d",&a[i][j]);

    //初始化前缀和
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= m; j ++)
            a[i][j] = a[i-1][j]+a[i][j-1]+a[i][j]-a[i-1][j-1];

    ll res = 0;
    //i,j枚举子矩阵的上下边界
    for(int i = 1; i <= n; i ++)
        for(int j = i; j <= n; j ++)
        //暴力枚举子矩阵的上下边界(C[n,2]种选法)
            for(int t = 1, s = 1; t <= m; t ++)//t为右边界
            {
                //对于每一个右边界t,找到一个满足条件的左边界s
                //维护一个滑动窗口[s,t]
                while(s<=t && Get(i,s,j,t)>k) //直到左右边界构成的矩阵满足条件
                    s++;
                if(s<=t)
                    res += t-s+1;
            }
            //t s指针都不回退,利用单调性维护一个窗口,进行窗口的缩减和扩张
    printf("%lld",res);
    return 0;
}

注意res要开long long,因为所有可能的子矩阵个数为 $C^2_{500}=6·10^{10}$,会爆int。

0 评论

App 内打开
你确定删除吗?
1024
x

© 2018-2025 AcWing 版权所有  |  京ICP备2021015969号-2
用户协议  |  隐私政策  |  常见问题  |  联系我们
AcWing
请输入登录信息
更多登录方式: 微信图标 qq图标 qq图标
请输入绑定的邮箱地址
请输入注册信息