「GXOI / GZOI2019」与或和
知识点:拆位,单调栈
前置知识:单调栈求解子矩阵数量 P1950 长方形。
简述
给定一 的数字矩阵 ,求该矩阵所有子矩阵中的数二进制与、二进制或的结果之和。答案对 取模。
,。
2S,512MB。
分析
二进制运算各位独立,考虑枚举二进制位,分别考虑每一位的贡献。问题变为对于枚举的二进制位,有多少子矩阵的数该位 二进制与/或 后结果为 1。将问题转化到了 01 矩阵上进行。
显然当某子矩阵的数该位均为 1 时,二进制与的结果才为 1。若该位均不为 0,则二进制或的结果为 1。问题转化为求 01 矩阵中多少个子矩阵是全 1 矩阵、非全 0 矩阵,第二问可以补集转化,求得所有全 0 矩阵并用矩阵总数减去即可。
考虑到每个子矩阵都由四条直线围成,每行/列的直线有 条( 个元素中空隙有 个),则子矩阵总数为 个。
考虑如何求全 1 矩阵的数量。确定矩阵的对角线即可确定矩阵,考虑 枚举子矩阵的右下角的点,合法的左端点的范围是一个梯形,合法的点的个数即为梯形面积。如下图所示:

问题变成如何在枚举右端点的同时维护上述梯形的面积。可以考虑以每个点为结尾的、纵向的最长的 1 的长度。在枚举裂时,用单调栈维护一个从顶至底递减的序列,储存上述梯形各部分的高,顺便维护面积即可。维护全 0 矩阵同理。
总复杂度 。
代码
复制复制//知识点:拆位,单调栈 /* 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; }
作者@Luckyblock,转载请声明出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 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】