P3941 入阵曲

知识点: 暴力,哈希表

原题面

一次原题大战的 T2,没看见 \(k\le 10^6\) 就上了哈希 = =
幸好单哈希没被卡(


题意简述

给定一 \(n\times m\) 的矩阵,格子 \((i,j)\) 内有正整数 \(a_{i,j}\)
给定参数 \(k\),求有多少个子矩阵的和是 \(k\) 的倍数。
\(1\le n,m\le 400\)\(1\le a_{i,j}<k\le 10^6\)
1S,256MB。


分析题意

以下讨论均在 \(\pmod k\) 下展开。
子矩阵的和是 \(k\) 的倍数,等价于子矩阵的和 \(\bmod\ k = 0\)

先考虑一维的情况(本题 \(m=0\) 的 subtask):

给定序列 和 参数 \(k\),求多少个子区间的和 \(\bmod \ k = 0\)

每一个子区间都可以通过两个前缀相减得到。
则每个 \(\bmod\ k = 0\) 的子区间,都与两个差为 \(\bmod\ k=0\) 的前缀和 一一对应
问题等价于统计差为 \(0\) 的前缀和的个数,在枚举前缀的同时维护一个桶,记录和不同的前缀的个数即可。
大概是这样的感觉:

cnt[0] = 1;
for (int i = 1; i <= n; ++ i) {
  ans += cnt[sum[i]];
  cnt[sum[i]] ++;
}

模数较大时可使用哈希表,期望复杂度 \(O(n)\)

再回到本题二维的情况。
发现数据范围较小,不妨 \(O(n^2)\) 暴力枚举子矩阵一条边的左右端点 \(l,r\)
将所有一条边为 \(l,r\),另一条边 长度为 1 的子矩阵的和看成一个数,即有下图形式:

mspaint 真 nm 难用

在确定了一条边的限制下,问题转化为上面的一维形式,直接套用上面的算法即可。
总复杂度 \(O(n^3)\) 级别。


代码实现

使用了 hash 表进行实现,可以处理更大的模数。
代码中枚举的是子矩阵的上边。

//知识点:暴力,哈希 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 400 + 10;
const LL kMod = 998244353;
const int kBase = 2333;
//=============================================================
int n, m, k, a[kMaxn][kMaxn];
int sum[kMaxn][kMaxn], tmp[kMaxn];
LL ans;
//=============================================================
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_;
}
namespace Hash {
  int e_num, head[kBase + 10], val[kMaxn];
  int cnt[kMaxn], ne[kMaxn];
  void Init() {
    e_num = 0;
    memset(head, 0, sizeof (head));
  }
  int Query(int val_) {
    int pos = val_ % kBase + 1;
    for (int i = head[pos]; i; i = ne[i]) {
      if (val[i] == val_) return cnt[i];
    }
    return 0;
  }
  void Insert(int val_) {
    int pos = val_ % kBase + 1;
    for (int i = head[pos]; i; i = ne[i]) {
      if (val[i] == val_) {
        cnt[i] ++; 
        return ;
      }
    }
    val[++ e_num] = val_;
    cnt[e_num] = 1;
    ne[e_num] = head[pos];
    head[pos] = e_num;
  }
}
//=============================================================
int main() {
  n = read(), m = read(), k = read();
  for (int i = 1; i <= n; ++ i) {
    for (int j = 1; j <= m; ++ j) {
      a[i][j] = read() % k;
      sum[i][j] = (sum[i][j - 1] + a[i][j]) % k;
    }
  }
  for (int l = 1; l <= m; ++ l) {
    for (int r = l; r <= m; ++ r) {
      Hash::Init();
      Hash::Insert(0);
      for (int i = 1; i <= n; ++ i) {
        tmp[i] = (tmp[i - 1] + sum[i][r] - sum[i][l - 1] + k) % k;
        ans += Hash::Query(tmp[i]);
        Hash::Insert(tmp[i]);
      }
    }
  }
  printf("%lld\n", ans);
  return 0;
}
posted @ 2020-10-27 17:49  Luckyblock  阅读(114)  评论(0编辑  收藏  举报