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 的子矩阵的和看成一个数,即有下图形式:
在确定了一条边的限制下,问题转化为上面的一维形式,直接套用上面的算法即可。
总复杂度 \(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;
}
作者@Luckyblock,转载请声明出处。