入阵曲:题解

  来源于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 现役

posted @ 2021-06-30 11:46  Geek_kay  阅读(38)  评论(0编辑  收藏  举报