Luogu3941[湖南集训2017] 入阵曲

原题链接:https://www.luogu.org/problemnew/show/P3941

入阵曲

题目背景

pdf题面和大样例链接:http://pan.baidu.com/s/1cawM7c 密码:xgxv

丹青千秋酿,一醉解愁肠。
无悔少年枉,只愿壮志狂。

题目描述

小 F 很喜欢数学,但是到了高中以后数学总是考不好。

有一天,他在数学课上发起了呆;他想起了过去的一年。一年前,当他初识算法竞赛的 时候,觉得整个世界都焕然一新。这世界上怎么会有这么多奇妙的东西?曾经自己觉得难以 解决的问题,被一个又一个算法轻松解决。

小 F 当时暗自觉得,与自己的幼稚相比起来,还有好多要学习的呢。

一年过去了,想想都还有点恍惚。

他至今还能记得,某天晚上听着入阵曲,激动地睡不着觉,写题写到鸡鸣时分都兴奋不已。也许,这就是热血吧。

也就是在那个时候,小 F 学会了矩阵乘法。让两个矩阵乘几次就能算出斐波那契数列的第 10100 项,真是奇妙无比呢。

不过,小F现在可不想手算矩阵乘法——他觉得好麻烦。取而代之的,是一个简单的小问题。他写写画画,画出了一个 n×m 的矩阵,每个格子里都有一个不超过 k 的正整数。

小F想问问你,这个矩阵里有多少个不同的子矩形中的数字之和是k的倍数?如果把一个子矩形用它的左上角和右下角描述为 (x1,y1,x2,y2) ,其中 x1x2,y1y2 ;那么,我们认为两个子矩形是不同的,当且仅当他们以 (x1,y1,x2,y2) 表示时不同;也就是说,只要两个矩形以 (x1,y1,x2,y2) 表示时相同,就认为这两个矩形是同一个矩形,你应该在你的答案里只算一次。

输入输出格式
输入格式:

从标准输入中读入数据。

输入第一行,包含三个正整数 n,m,k

输入接下来 n 行,每行包含m个正整数,第 i 行第j列表示矩阵中第 i 行第j列中所填的正整数 ai,j

输出格式:

输出到标准输出中。

输入一行一个非负整数,表示你的答案。

输入输出样例
输入样例#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×(n1)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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShadyPi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值