「GXOI / GZOI2019」与或和

知识点:拆位,单调栈

原题面:LojLuogu

前置知识:单调栈求解子矩阵数量 P1950 长方形

简述

给定一 n×n 的数字矩阵 A,求该矩阵所有子矩阵中的数二进制与、二进制或的结果之和。答案对 109+7 取模。
1n1030Ai,j2311
2S,512MB。

分析

二进制运算各位独立,考虑枚举二进制位,分别考虑每一位的贡献。问题变为对于枚举的二进制位,有多少子矩阵的数该位 二进制与/或 后结果为 1。将问题转化到了 01 矩阵上进行。
显然当某子矩阵的数该位均为 1 时,二进制与的结果才为 1。若该位均不为 0,则二进制或的结果为 1。问题转化为求 01 矩阵中多少个子矩阵是全 1 矩阵、非全 0 矩阵,第二问可以补集转化,求得所有全 0 矩阵并用矩阵总数减去即可。

考虑到每个子矩阵都由四条直线围成,每行/列的直线有 n+1 条(n 个元素中空隙有 n+1 个),则子矩阵总数为 (n+12)2 个。

考虑如何求全 1 矩阵的数量。确定矩阵的对角线即可确定矩阵,考虑 O(n2) 枚举子矩阵的右下角的点,合法的左端点的范围是一个梯形,合法的点的个数即为梯形面积。如下图所示:

车万出新作了nicenicenicenicenicenicenicenicenice

问题变成如何在枚举右端点的同时维护上述梯形的面积。可以考虑以每个点为结尾的、纵向的最长的 1 的长度。在枚举裂时,用单调栈维护一个从顶至底递减的序列,储存上述梯形各部分的高,顺便维护面积即可。维护全 0 矩阵同理。
总复杂度 O(n2)

代码

复制复制
//知识点:拆位,单调栈
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <stack>
#define LL long long
const int kN = 1e3 + 10;
const int mod = 1e9 + 7;
//=============================================================
int n, all, ans1, ans2, a[kN][kN], maxx[2][kN];
int s[2], top[2], st[2][kN];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Chkmax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
int Insert(int x_, int pos_) {
while (top[x_] && maxx[x_][pos_] < maxx[x_][st[x_][top[x_]]]) {
s[x_] -= 1ll * maxx[x_][st[x_][top[x_]]] *
(st[x_][top[x_]] - st[x_][top[x_] - 1]) % mod;
s[x_] = (s[x_] + mod) % mod;
-- top[x_];
}
st[x_][++ top[x_]] = pos_;
s[x_] += 1ll * maxx[x_][pos_] * (pos_ - st[x_][top[x_] - 1]) % mod;
s[x_] %= mod;
return s[x_];
}
void Solve(int k_) {
memset(maxx, 0, sizeof (maxx));
int sum1 = 0, sum2 = all;
for (int i = 1; i <= n; ++ i) {
top[0] = top[1] = s[0] = s[1] = 0;
for (int j = 1; j <= n; ++ j) {
maxx[0][j] = (a[i][j] >> k_ & 1) ? 0 : maxx[0][j] + 1;
maxx[1][j] = (a[i][j] >> k_ & 1) ? maxx[1][j] + 1 : 0;
sum1 = 1ll * (sum1 + Insert(1, j)) % mod;
sum2 = 1ll * (sum2 - Insert(0, j) + mod) % mod;
}
}
ans1 = 1ll * (ans1 + (1ll << k_) % mod * sum1 % mod) % mod;
ans2 = 1ll * (ans2 + (1ll << k_) % mod * sum2 % mod) % mod;
}
//=============================================================
int main() {
n = read();
all = 1ll * n * (n + 1) / 2ll % mod; //所有子矩阵的数量
all = 1ll * n * (n + 1) / 2ll % mod * all % mod;
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= n; ++ j) {
a[i][j] = read();
}
}
for (int k = 0; k <= 30; ++ k) Solve(k);
printf("%d %d\n", ans1, ans2);
return 0;
}
posted @   Luckyblock  阅读(88)  评论(1编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示