洛谷p5300 与或和 求全1/0矩阵个数 单调栈

博客围绕矩阵与或和问题展开,定义了矩阵的AND值和OR值,要求计算给定N×N矩阵所有子矩阵的AND值与OR值之和并对109+7取模。思路是对二进制每位分别计算,将其转化为01矩阵,用单调栈求全1/0矩阵。最后还给出了相关代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题 C: 与或和

时间限制: 4 Sec  内存限制: 512 MB
提交: 275  解决: 76
[提交] [状态] [命题人:admin]

题目描述

Freda 学习了位运算和矩阵以后,决定对这种简洁而优美的运算,以及蕴含深邃空间的结构进行更加深入的研究。

对于一个由非负整数构成的矩阵,她定义矩阵的AND值为矩阵中所有数二进制AND(&)的运算结果;定义矩阵的OR值为矩阵中所有数二进制OR(|)的运算结果。

给定一个N×N的矩阵,她希望求出:

该矩阵的所有子矩阵的AND值之和(所有子矩阵AND值相加的结果)。
该矩阵的所有子矩阵的OR值之和(所有子矩阵OR值相加的结果)。
接下来的剧情你应该已经猜到——Freda并不想花费时间解决如此简单的问题,所以这个问题就交给你了。

由于答案可能非常的大,你只需要输出答案对109+7取模后的结果。

 

输入

第一行是一个正整数N,表示矩阵的尺寸。
接下来N行,每行N个自然数,代表矩阵的一行。相邻两个自然数之间由一个或多个空格隔开。

 

输出

输出只有一行,包含两个用空格隔开的整数,第一个应为所有子矩阵AND值之和除以 109+7的余数,第二个应为所有子矩阵OR值之和除以109+7的余数。

 

样例输入

复制样例数据

3
1 0 0
0 0 0
0 0 0

样例输出

1 9

 

提示

该3×3矩阵共有9个1×1子矩阵、6个1×2子矩阵、6个2×1子矩阵4个2×2子矩阵、3个1×3子矩阵、3 个3×1子矩阵、2个2×3子矩阵、2个3×2子矩阵和1个3×3子矩阵。
只有一个子矩阵(仅由第一行第一列的那个元素构成的1×1矩阵)AND值为1,其余子矩阵的AND值均为0,总和为1。
包含第一行第一列那个元素的子矩阵有9个,它们的OR值为1,其余子矩阵的OR值为0 ,总和为9。

 

 

 

[提交][状态]

中文题

思路:对01矩阵来说,and就是找矩阵中存在多少全1子矩阵,or是子矩阵存在1即有贡献,那么就求全0矩阵,再相减

那么对其他数来说,用二进制每位分别计算,这样对每一位都是01矩阵

求全1/0矩阵是个单调栈思路的模板,这个板子的思路大概就是对(i,j)来说,计算以该位置为右下位置为矩阵个数

单调栈里存的一直都是列位置长度最小的

举一个例子

1 0 1 1 0 1
1 1 0 1 1 1
1 1 1 1 1 1

对第三行来说,

第一列为make_pair(3,1),sum+=3,即第一列三个1构成的全1矩阵个数,sum=3,ans=3

第二列为(2,2),取代栈里第一个位置,sum-=3,sum+=4,4即位置(2,1),(2,2),(3,1),(3,2)组成的矩阵,sum=4,ans=7

第三列为(1,3),再次取代第一个,sum-=4,sum+=3,3即(3,1),(3,2),(3,3)组成的矩阵,这时候sum=3,ans=10

第四列为(3,1),top++,存在栈第二个位置,sum+=3,原来的3这里重新加了一次,代表(3,2),(3,3),(3,4)位置的矩阵,新加上的3代表(1,4),(2,4),(3,4)位置的矩阵,sum=6,ans=16

第五列为(2,2),取代栈里第二个位置,即sum-=3,sum+=4,原来的3还是没减去,代表(3,3),(3,4),(3,5)位置的矩阵,新加上的4代表(2,4),(2,5),(3,4),(3,5)位置的矩阵,sum=7,ans=23

第六列为(3,1),top++,存在栈第三个位置,即sum+=3,原来的7分为3和4,3代表(3,4),(3,5),(3,6)位置的矩阵,4代表(2,5),(2,6),(3,5),(3,6)位置的矩阵,新加上的3代表(1,6),(2,6),(3,6)位置的矩阵,sum=10,ans=33

代码:

#include <bits/stdc++.h>

typedef long long ll;
using namespace std;
const int maxn = 1e3 + 100;
const int mod = 1e9 + 7;
int n;
ll a[maxn][maxn];
ll col[maxn];
pair<ll, ll> stac[maxn];

ll get(int bit, int f) { //求全1/0矩阵的个数
    ll ans = 0, sum = 0, row = 1;
    int top = 0;
    for (int i = 0; i <= n; i++)
        col[i] = 0;
    for (int i = 1; i <= n; i++) {
        sum = 0, top = 0;
        for (int j = 1; j <= n; j++) {
            row = 1; //记录第i行第j列位置往左最长连续全1/0区间长度
            col[j] = ((a[i][j] >> bit) & 1) == f ? col[j] + 1 : 0; //记录第i行第j列位置往上最长连续全1/0区间长度
            while (top && stac[top].first >= col[j]) {
                sum -= stac[top].first * stac[top].second;
                row += stac[top].second;
                top--;
            }
            sum = (col[j] * row + sum) % mod;
            ans = (ans + sum) % mod;
            stac[++top] = make_pair(col[j], row);
        }
    }
    return ans % mod;
}

int main() {
    scanf("%d", &n);
    ll su = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            scanf("%lld", &a[i][j]);
            su = (su + 1ll * i * j) % mod;
        }
    }
    ll ans1 = 0, ans2 = 0, p = 1;
    for (int i = 0; i <= 30; i++) {
        ans1 = (ans1 + get(i, 1) * p % mod) % mod;
        ans2 = (ans2 + (su - get(i, 0)) * p % mod) % mod;
        p <<= 1;
    }
    printf("%lld %lld\n", (ans1 + mod) % mod, (ans2 + mod) % mod);
    return 0;
}

顺便存一下板子

typedef long long ll;
const int maxn = 1e3 + 100;
const int mod = 1e9 + 7;
int n;
ll a[maxn][maxn];
ll col[maxn];
pair<ll, ll> stac[maxn];

ll get(int bit, int f) {
    ll ans = 0, sum = 0, row = 1;
    int top = 0;
    for (int i = 0; i <= n; i++)
        col[i] = 0;
    for (int i = 1; i <= n; i++) {
        sum = 0, top = 0;
        for (int j = 1; j <= n; j++) {
            row = 1; 
            col[j] = ((a[i][j] >> bit) & 1) == f ? col[j] + 1 : 0; 
            while (top && stac[top].first >= col[j]) {
                sum -= stac[top].first * stac[top].second;
                row += stac[top].second;
                top--;
            }
            sum = (col[j] * row + sum) % mod;
            ans = (ans + sum) % mod;
            stac[++top] = make_pair(col[j], row);
        }
    }
    return ans % mod;
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值