入阵曲:题解
来源于NOIP模拟10;
Description:
丹青千秋酿,一醉解愁肠。
无悔少年枉,只愿壮志狂。
小 F 很喜欢数学,但是到了高中以后数学总是考不好。有一天,他在数学课上发起了呆; 他想起了过去的一年。 一年前,当他初识算法竞赛的时候,觉得整个世界都焕然一新。 这世界上怎么会有这么多奇妙的东西? 曾经自己觉得难以解决的问题,被一个又一个算法轻松解决。
小 F 当时暗自觉得,与自己的幼稚相比起来,还有好多要学习的呢。 一年过去了,想想都还有点恍惚。
他至今还能记得,某天晚上听着入阵曲,激动地睡不着觉,写题写到鸡鸣时分都兴奋不已。也许,这就是热血吧。
也就是在那个时候,小 F 学会了矩阵乘法。让两个矩阵乘几次就能算出斐波那契数列的第n项, 真是奇妙无比呢。
不过, 小 F 现在可不想手算矩阵乘法——他觉得好麻烦。取而代之的,是一个简单的小问题。 他写写画画, 画出了一个 的矩阵,每个格子里都有一个不超过 的正整数。 小 F 想问问你, 这个矩阵里有多少个 不同的 子矩形中的数字之 和 是 的倍数?
如果把一个子矩形用它的左上角和右下角描述为 ,其中 , ;那么,我们认为两个子矩形是不同的,当且仅当他们以 表示时不同; 也就是说, 只要两个矩形以(x1,y1,x2,y2)表示时相同,就认为这两个矩形是同一个矩形,你应该在你的答案里只算一次。
输入格式
从文件rally.in中读入数据。
输入第一行,包含三个正整数。
输入接下来 行, 每行包含 个正整数, 第 行第 列表示矩阵中第 行第 列
中所填的正整数。
输出格式
输出到文件rally.out中。
输入一行一个非负整数,表示你的答案。
样例
样例输入1
2 3 2
1 2 1
2 1 2
样例输出1
6
基本思路:
这一题考场上是用的二维前缀和打的, O O O( n 2 m 2 n^{2}m^{2} n2m2)的效率,60到手,思路很简单,就直接放代码了
#include<bits/stdc++.h>
using namespace std;
namespace Decleration
{
#define ll long long
#define rr register
const int SIZE=404;
ll n,m,k;
ll a[SIZE][SIZE];
ll read()
{
rr ll x_read=0,y_read=1;
rr char c_read=getchar();
while(c_read<'0'||c_read>'9')
{
if(c_read=='-') y_read=-1;
c_read=getchar();
}
while(c_read<='9'&&c_read>='0')
{
x_read=(x_read<<3)+(x_read<<1)+(c_read^48);
c_read=getchar();
}
return x_read*y_read;
}
};
using namespace Decleration;
int main()
{
int ans=0;
n=read(),m=read(),k=read();
for(rr int i=1;i<=n;i++)
for(rr int j=1;j<=m;j++)
a[i][j]=read()+a[i][j-1];
for(rr int i=1;i<=m;i++)
for(rr int j=1;j<=n;j++)
a[j][i]+=a[j-1][i];
for(rr int x1=1;x1<=m;x1++)
for(rr int x2=x1;x2<=m;x2++)
for(rr int y1=1;y1<=n;y1++)
for(rr int y2=y1;y2<=n;y2++)
{
ll sum=a[y2][x2]-a[y2][x1-1]-a[y1-1][x2]+a[y1-1][x1-1];
if(sum%k==0) ans++;
}
printf("%d\n",ans);
}
说说正解。
正解是利用了取余的一个性质:
两个数如果对于同一个数余数相等,那么他们之差能被这个数整除
(作者猛拍脑门)Emmmm……好简单啊我为什么没想出呢
对于任何一个矩阵,他都可以表示为两个矩阵相减,那么问题就转化为了统计和模上k结果相等的矩阵有多少对。
我们可以用两个循环控制上下界,一个循环从左往右扫,然后维护若干个桶,记录余数为i的矩阵有多少个,每扫到一个矩阵,就往答案里累加当前对应桶里存的值,然后将桶++
上代码(内附若干注意事项):
#include<bits/stdc++.h>
using namespace std;
namespace Decleration
{
#define ll long long
#define rr register
const int SIZE=404,SIZEK=1e6+4;
ll n,m,K;
ll reco[SIZEK];
ll a[SIZE][SIZE];
ll read()
{
rr ll x_read=0,y_read=1;
rr char c_read=getchar();
while(c_read<'0'||c_read>'9')
{
if(c_read=='-') y_read=-1;
c_read=getchar();
}
while(c_read>='0'&&c_read<='9')
{
x_read=(x_read<<3)+(x_read<<1)+(c_read^48);
c_read=getchar();
}
return x_read*y_read;
}
};
using namespace Decleration;
int main()
{
n=read(),m=read(),K=read();
//计算二维前缀和:
for(rr int i=1;i<=n;i++)
for(rr int j=1;j<=m;j++)
a[i][j]=read()+a[i][j-1];
for(rr int i=1;i<=m;i++)
for(rr int j=1;j<=n;j++)
a[j][i]+=a[j-1][i];
//
rr ll ans=0;//一定是long long
for(rr int i=1;i<=n;i++)
for(rr int j=i;j<=n;j++)
{
reco[0]=1;
for(rr int k=1;k<=m;k++)
{
int mod=(a[j][k]-a[i-1][k])%K;
ans+=reco[mod];
reco[mod]++;
}
for(rr int k=0;k<=m;k++)
reco[(a[j][k]-a[i-1][k])%K]=0;
}
printf("%lld\n",ans);
}
总结:
这题主要考察了两个东西:
1.前缀和优化
2.取余的性质
其中关于取余的地方是比较生疏的,很小的一个点。
2021.6.30 现役