「GXOI / GZOI2019」与或和

知识点:拆位,单调栈

原题面:LojLuogu

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

简述

给定一 \(n\times n\) 的数字矩阵 \(A\),求该矩阵所有子矩阵中的数二进制与、二进制或的结果之和。答案对 \(10^9 + 7\) 取模。
\(1\le n\le 10^3\)\(0\le A_{i,j}\le 2^{31} - 1\)
2S,512MB。

分析

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

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

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

车万出新作了nicenicenicenicenicenicenicenicenice

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

代码

//知识点:拆位,单调栈
/*
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 @ 2021-02-28 19:10  Luckyblock  阅读(80)  评论(1编辑  收藏  举报