【问题标题】:Number of parallelograms on a NxM gridNxM 网格上的平行四边形数
【发布时间】:2014-01-28 05:42:30
【问题描述】:

当给定一个网格大小 N x M 时,我必须解决一个问题,我必须找到“可以放入其中”的平行四边形的数量,这样它们的每个坐标都是一个整数。

这是我的代码:

/*
      ~Keep It Simple!~
*/

#include<fstream>

#define MaxN 2005

int N,M;
long long Paras[MaxN][MaxN]; // Number of parallelograms of Height i and Width j
long long Rects; // Final Number of Parallelograms

int cmmdc(int a,int b)
{
while(b)
{
    int aux = b;
    b = a -(( a/b ) * b);
    a = aux;
}

return a;
}

int main()
{
freopen("paralelograme.in","r",stdin);
freopen("paralelograme.out","w",stdout);

scanf("%d%d",&N,&M);

for(int i=2; i<=N+1; i++)
    for(int j=2; j<=M+1; j++)
    {
        if(!Paras[i][j])
          Paras[i][j] = Paras[j][i] = 1LL*(i-2)*(j-2) + i*j - cmmdc(i-1,j-1) -2; // number of parallelograms with all edges on the grid + number of parallelograms with only 2 edges on the grid.
        Rects += 1LL*(M-j+2)*(N-i+2) * Paras[j][i]; // each parallelogram can be moved in (M-j+2)(N-i+2) places.
    }

printf("%lld", Rects);
}

示例:对于 2x2 网格,我们有 22 个可能的平行四边形。

我的算法有效并且是正确的,但我需要让它更快一点。我想知道怎么可能。

附:我听说我应该预处理最大公约数并将其保存在一个数组中,这会将运行时间减少到 O(n*m),但我不知道如何在不使用 cmmdc 的情况下做到这一点(最大公约数)函数。

【问题讨论】:

  • 嗨。我很确定你的计算机科学老师只是问你这个问题,让你知道这个问题有多难。您可能会学习的下一件事是基于格问题的密码学(也就是量子计算机无法破解的下一件大事:-)
  • 这不是我的计算机科学老师问我的事情,我的知识可能比我的计算机科学老师多十倍。只是我在为我国的算法奥林匹克做准备。

标签: c++ algorithm mathematical-lattices


【解决方案1】:

确保N不小于M:

if( N < M ){ swap( N, M ); }

利用循环中的对称性,您只需将 j 从 2 运行到 i:

for(int j=2; j<=min( i, M+1); j++)

你不需要额外的数组Paras,放下它。而是使用临时变量。

long long temparas = 1LL*(i-2)*(j-2) + i*j - cmmdc(i-1,j-1) -2;
long long t1 = temparas * (M-j+2)*(N-i+2);
Rects += t1;
// check if the inverse case i <-> j must be considered
if( i != j && i <= M+1 ) // j <= N+1 is always true because of j <= i <= N+1
    Rects += t1;

使用余数运算符替换此行:b = a -(( a/b ) * b);

b = a % b;

缓存 cmmdc 结果可能是可能的,您可以使用某种筛选算法初始化数组:创建一个由 a 和 b 索引的二维数组,在 a 和 b 是 2 的倍数的每个位置放置“2”,然后在 a 和 b 是 3 的倍数的每个位置放一个“3”,以此类推,大致如下:

int gcd_cache[N][N];

void init_cache(){
    for (int u = 1; u < N; ++u){
        for (int i = u; i < N; i+=u ) for (int k = u; k < N ; k+=u ){
            gcd_cache[i][k] = u;
        }
    }
}

不确定它是否有很大帮助。

【讨论】:

  • 您的前 2 个想法似乎加快了速度,但产生了一半正确的来源。使用我第一次发布的来源,我得到了所有测试的所有正确答案(除了一个,这是错过了时间限制)。有了你的资料,我只能得到 50% 的测试正确,而且都在时间限制内。
  • 我找到了原因并编辑了答案,这两种情况的 t1 是不一样的。但有了这个解决方案,它仍然没有停留在时间限制内。
【解决方案2】:

您的代码中的第一条注释声明“保持简单”,因此,鉴于此,为什么不尝试用数学方法解决问题并打印结果。

如果您从网格中选择两条长度为 N 的线,您将通过以下方式找到平行四边形的数量:

  • 选择两行中相邻的两个点:有(N-1)^2 这样做的方法,因为您可以将两个点定位在N-1 每一行的位置。
  • 在两行中选择两个点,它们之间有一个空格:有(N-2)^2 的方法。
  • 选择两个点,它们之间有两个、三个和最多N-2 个空格。
  • 产生的组合数将为(N-1)^2+(N-2)^2+(N-3)^2+...+1
  • 通过求和,我们得到公式:1/6*N*(2*N^2-3*N+1)。检查WolframAlpha 进行验证。

现在您已经有了两行的解决方案,您只需将其乘以 M 的 2 阶 combinations 的数量,即 M!/(2*(M-2)!)

因此,整个公式将是:1/12*N*(2*N^2-3*N+1)*M!/(M-2)!,其中! 标记表示factorial^ 表示幂运算符(请注意,相同的符号不是 C++ 中的幂运算符,而是按位 XOR 运算符)。

与遍历矩阵相比,此计算所需的操作更少。

【讨论】:

  • 我确实在数学上解决了它,上面有 2 个公式,问题是,我需要它在 0.4 秒内在 2000x2000 网格上运行。 Keep it simple 标签是我在我制作的每个程序中添加的内容
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-27
  • 2010-11-16
  • 1970-01-01
  • 2012-11-10
  • 2010-09-19
  • 2015-01-02
相关资源
最近更新 更多