原题链接:https://www.luogu.org/problemnew/show/P3941
入阵曲
题目背景
pdf题面和大样例链接:http://pan.baidu.com/s/1cawM7c 密码:xgxv
丹青千秋酿,一醉解愁肠。
无悔少年枉,只愿壮志狂。
题目描述
小 F 很喜欢数学,但是到了高中以后数学总是考不好。
有一天,他在数学课上发起了呆;他想起了过去的一年。一年前,当他初识算法竞赛的 时候,觉得整个世界都焕然一新。这世界上怎么会有这么多奇妙的东西?曾经自己觉得难以 解决的问题,被一个又一个算法轻松解决。
小 F 当时暗自觉得,与自己的幼稚相比起来,还有好多要学习的呢。
一年过去了,想想都还有点恍惚。
他至今还能记得,某天晚上听着入阵曲,激动地睡不着觉,写题写到鸡鸣时分都兴奋不已。也许,这就是热血吧。
也就是在那个时候,小 F 学会了矩阵乘法。让两个矩阵乘几次就能算出斐波那契数列的第 10100 项,真是奇妙无比呢。
不过,小F现在可不想手算矩阵乘法——他觉得好麻烦。取而代之的,是一个简单的小问题。他写写画画,画出了一个 n×m 的矩阵,每个格子里都有一个不超过 k 的正整数。
小F想问问你,这个矩阵里有多少个不同的子矩形中的数字之和是
输入输出格式
输入格式:
从标准输入中读入数据。
输入第一行,包含三个正整数 n,m,k 。
输入接下来
n
行,每行包含
输出格式:
输出到标准输出中。
输入一行一个非负整数,表示你的答案。
输入输出样例
输入样例#1:
2 3 2
1 2 1
2 1 2
输出样例#1:
6
说明
【样例 1 说明】
这些矩形是符合要求的: (1, 1, 1, 3),(1, 1, 2, 2),(1, 2, 1, 2),(1, 2, 2, 3),(2, 1, 2, 1),(2, 3, 2, 3)。
子任务会给出部分测试数据的特点。如果你在解决题目中遇到了困难,可以尝试只解 决一部分测试数据。
每个测试点的数据规模及特点如下表:
特殊性质:保证所有
ai,j
均相同。
题解
博主考试的时候当然用最简单粗暴的思路:O(n^4)枚举矩形坐标,得完60就溜。。。
后面大佬难题选讲又讲到这道题,才了解到此题的 O(n3) 正解,我们依然 O(n2) 预处理出矩阵在模k意义下的前缀和。接下来,我们枚举矩形的上下界, O(n) 从左往右遍历记录上下界内的矩形前缀和模k后的余数出现了多少次,因为两个余数相同的前缀和之间的矩形肯定能被k整除。最后排列组合一下,有 n×(n−1)2 种组合方式,其实就可以化为一个等差数列在统计的时候顺便求出来。
代码
#include<bits/stdc++.h>
using namespace std;
const int M=405;
const int N=1e6+5;
int sq[M][M],bak[N],hh[N],n,m,k;
long long ans;
void in()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
scanf("%d",&sq[i][j]);
}
void pre()
{
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
sq[i][j]+=sq[i][j-1],sq[i][j]%=k;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
sq[i][j]+=sq[i-1][j],sq[i][j]%=k;
}
void ac()
{
pre();
for(int i=0;i<n;++i)
for(int j=i+1;j<=n;++j)
{
bak[0]=1;
for(int p=1;p<=m;++p)
{
hh[p]=(sq[j][p]-sq[i][p])%k;
if(hh[p]<0)hh[p]+=k;
ans+=bak[hh[p]];
bak[hh[p]]++;
}
for(int p=1;p<=m;++p)
bak[hh[p]]=0;
}
printf("%lld",ans);
}
int main()
{
in();ac();
return 0;
}