zoukankan      html  css  js  c++  java
  • HDU4483 Lattice triangle 数论

    题意:给定一个N*N个网格(每条边上共有N+1个点),从这个网格中取出三个点构成三角形,问一共可以构成多少种三角形。

    解法:首先令N' = N+1,那么不考虑直线相交的情况下从N'*N'个点中选出三个点的方案数为C(3, N'*N');然后考虑到每条平行于水平和垂直线的线段上共有2*N'*C(3, N')种情况需要减去,最后还要减去斜线直线上的情况。

    斜线上则只考虑从左上到右下的情况,乘以2表示从右到左是对称的。对于每一种斜线的情况都可以将左上端点和右下端点固定,然后就可以看作是一个矩形对角线上除去两个端点外,中间还有多少个点的问题了,对于这个问题,在求凸包上有多少个整数点时应该知道,假设边为(a, b),线段上点的个数为gcd(a, b) + 1,除去两个端点后为gcd(a, b) - 1,因此枚举出所有的矩形就可以了。

    对于边长为(a, b)的矩形,N*N的矩形中共有(N' - a) * (N' - b)个。因此边长为(a, b)的情况下,共有(N' - a) * (N' - b) * (gcd(a, b) - 1)中不合法的情况需要减去。但是该题由于N的范围过大,因此在枚举边长是达到了O(N^2)的时间复杂度,肯定会超时的。因此需要优化这个求解的过程:

    具体做法是枚举gcd值,由于gcd值至少为2才会对结果有影响,因此gcd值就可以从2开始枚举起,且这个gcd值最大为N。若枚举了gcd值为k,那么有gcd(a, b) = k,则gcd(a/k, b/k) = 1,因此只要知道在某一范围内互质数的对数,然后再将这些互质的数均乘上一个x即为所求,约定a <= b,由于a、b的取值均为1-N,因此b的取值最大为N / x,那么互质数的对数就是2 * (phi[1] + phi[2] + ... + phi[N / x]),乘以2是因为a,b可以交换位置,后面的求和是因为这么些互质的数均满足要求。那么对于每一组互质的数m, n,有m = a/k, b = b/k,即a = k*m, b = k*n,因此代入到(N' - a) * (N' - b) * (gcd(a, b) - 1)有(N'*N' - (m + n)*N'*k + m*n*k*k) * (k - 1)。这是对于一组来说,由于枚举k值,因此k值相同的数对应一起计算,因此可知所有k值相同的数对,N'*N'的系数为前面所讨论的总对数,N'*k以及k*k的系数由互质对的成对情况分别能够求出递推式。成对是指若gcd(y, N) = 1,那么gcd(N-y, N) = 1 (y < N)。

    维护好上面提到的三个系数便可以计算出最后的结果了,计算过程中应防止溢出,代码如下:

    #include <cstdlib>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    typedef long long LL;
    const int MaxN = 100000;
    const int MOD = int(1e9+7);
    int phi[MaxN+5];
    int spair[MaxN+5];
    int sadd[MaxN+5];
    int smul[MaxN+5];
    
    void Euler() {
        phi[1] = 1;
        spair[1] = 1, sadd[1] = 2, smul[1] = 1;
        for (int i = 2; i <= MaxN; ++i) {
            if (!phi[i]) {
                for (int j = i; j <= MaxN; j += i) {
                    if (!phi[j]) phi[j] = j;
                    phi[j] -= phi[j] / i;
                }
            }
            // 由于于i互质的数一定是互补的即gcd(x, n) = 1,则gcd(n-x, n) = 1 
            spair[i] = (1LL * spair[i-1] + phi[i] * 2) % MOD;
            sadd[i]  = (1LL * sadd[i-1] + 1LL * phi[i] * 3 % MOD * i) % MOD;
            smul[i]  = (1LL * smul[i-1] + 1LL * phi[i] * i % MOD * i) % MOD;
        }
    }
    
    LL cal(LL x[]) {
        // 因为有除法,因此要将除数先进行处理
        for (int i = 0; i < 3; ++i) {
            if (x[i] % 2 == 0) {
                x[i] /= 2;
                break;
            }
        }
        for (int i = 0; i < 3; ++i) {
            if (x[i] % 3 == 0) {
                x[i] /= 3;
                break;
            }
        }
        for (int i = 0; i < 3; ++i) x[i] %= MOD;
        return x[0]%MOD*x[1]%MOD*x[2]%MOD;
    }
    
    int main() {
        Euler();
        int T, N;
        scanf("%d", &T);
        while (T--) {
            LL ret, exp = 0;
            scanf("%d", &N); // 坐标的取值范围[0,N],共有N+1种选择 
            ++N;
            LL a[3] = {1LL*N*N, 1LL*N*N-1, 1LL*N*N-2};
            LL b[3] = {N, N-1, N-2};
            ret = cal(a);
            ret = ((ret - cal(b)*2%MOD*N%MOD) % MOD + MOD) % MOD;
            // 也可将mod乘以6,先做乘法然后mod这个新的数,相当于把余数扩大了6倍,然后再除以6 
            for (int k = 2; k < N; ++k) {
                int t = (N-1) / k;
                exp = (exp + 1LL*(k-1)*(1LL*spair[t]*N%MOD*N%MOD))%MOD;
                exp = ((exp - 1LL*(k-1)*(1LL*sadd[t]*k%MOD*N%MOD)%MOD)%MOD + MOD) % MOD;
                exp = (exp + 1LL*(k-1)*k%MOD*k%MOD*smul[t]%MOD)%MOD;
            }
            ret = ((ret-2*exp)%MOD+MOD)%MOD;
            printf("%I64d\n", ret);
        }
        return 0;
    }
  • 相关阅读:
    mybatis中resultMap配置细则
    关于mybatis中typeHandler的两个案例
    mybatis映射器配置细则
    mybatis常用配置
    初识mybatis(二)
    初识mybatis
    数值优化(Numerical Optimization)学习系列-无梯度优化(Derivative-Free Optimization)
    交替方向乘子法(ADMM)的原理和流程的白话总结
    用ADMM求解大型机器学习问题
    数值优化(Numerical Optimization)学习系列-目录
  • 原文地址:https://www.cnblogs.com/Lyush/p/3132174.html
Copyright © 2011-2022 走看看