Pythagoras HDU - 6211(法里数列构造+本原勾股数组+本地预处理不出来。。)

Pythagoras HDU - 6211

Given a list of integers a0,a1,a2,⋯,a2k−1. Pythagoras triples over 109 are all solutions of x2+y2=z2 where x,y and z are constrained to be positive integers less than or equal to 109. You are to compute the sum of ay mod 2k of triples (x,y,z) such that x < y < z and they are relatively prime, i.e., have no common divisor larger than 1
.
Input
The first line is an integer T (1≤T≤3) indicating the total number of cases.
For each test case the first line is the integer k (1≤k≤17).
The second line contains 2k integers corresponding to a0 to a2k−1, where each ai satisfies 1≤ai≤255
.
Output
For each case output the sum of ay mod 2k
in a line.
Sample Input

3
2
0 0 0 1
2
1 0 0 0
2
1 1 1 1

Sample Output

39788763
79577506
159154994

题意:

给出 2 k a i ,求出所有满足 x , y , z x < y < z < 1 e 9 a y % 2 k 是多少

分析:

这题是个很迷的题,各种神奇

首先学习到了对于取模2的幂次来说,可以进行交换(我没找到相关定理,只是根据题解推出来的,要不然这题不可能对),什么意思呢,就是比如一个数 n ,计算 n % 2 k 1 % 2 k 2 ,如果 k 1 > k 2 ,那么 n % 2 k 1 % 2 k 2 = n % 2 k 2 ,举个具体的例子来说 9 % 8 % 2 = 1 = 9 % 2 = 1

这个结论貌似只对取模2的幂有效,对于一般数的取模是肯定不对的(我没找到相关定理解释,但是猜测是对的,因为这道题对了,我也是看着他们的题解)

下面先说一下这道题,很明显我们需要得到1e9内的本原勾股数组,并且我们知道本原勾股数组公式为:

( 2 m n , m 2 n 2 , m 2 + n 2 )

m n 互质, m n 有一奇一偶,即 m n 是奇数

维基百科原话这样说:

Euclid’s formula[3] is a fundamental formula for generating Pythagorean triples given an arbitrary pair of integers m and n with m > n > 0. The formula states that the integers

a = m 2 n 2 ,   b = 2 m n ,   c = m 2 + n 2

form a Pythagorean triple. The triple generated by Euclid’s formula is primitive if and only if m and n are coprime and not both odd. When both m and n are odd, then a, b, and c will be even, and the triple will not be primitive; however, dividing a, b, and c by 2 will yield a primitive triple when m and n are coprime and both odd.[4]

因此目标是枚举出互质的数对,然后判断m-n是奇数那么就找到了一个勾股数

枚举互质的数对这时有一个神奇的方法,就是法里数列,构造法里数列的方式以一个叫Stern-Brocot tree的东西,其实就是递归生成n阶的法里数列,法里数列有非常良好的性质,其中一个就是得到的分数分子分母都是互质的,而且是全部的n内的互质对构成的真分数(具体关于法里数列的内容去网上找吧我这里有有个法里数列csdn,可能不全,可以看看维基百科),这样就能得到n内互质数对,然后判断就能得到本原勾股数组

根据上面我们说的取模2的幂次的性质,因为y大小会达到接近1e9的规模,而每次取模最大 2 17 ,所以预处理的时候就直接取模 2 17 好了,然后这样预处理出每个y取模后对应下标出现了几次

在得到每个数列a的时候,遍历1到 2 17 ,看每个y的贡献次数,然后在乘上这个实际取模 2 k 下标的那个a数列中的值,求和即可

而且这里取模是用位运算做的,结论取模2的幂次的数,相当于和这个数-1按位与即
m o d     2 k = n & ( ( 1 << k ) 1 )

最后一个神奇的地方,网上有人说法里数列Stern-Brocot tree构造的时间复杂度是 O ( n 2 ) 的,不知道怎么算,感觉这个复杂度恨不能理解,怎么不超时呢?我用clcok计算了一下时间花费发现1e5的时候0ms,1e8的时候500多毫秒,1e9的时候就栈溢出了,所以本地跑不出来,你也测不了样例,提交就是过,测评姬真强,哈 哈。。。

code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1 << 17;
const int MAX = 1e9;

int k;
int a[maxn+50];
int ans[maxn+50];

void solve(int l1,int r1,int l2,int r2){
    int ml = (l1 + l2);
    int mr = (r1 + r2);

    if((ll)ml * ml + (ll)mr * mr > MAX) return;
    if((mr - ml) & 1){
        ans[max(mr*mr-ml*ml,2*ml*mr)&(maxn-1)]++;
    }
    solve(l1,r1,ml,mr);
    solve(ml,mr,l2,r2);
}
int main(){
    int T;
    scanf("%d",&T);
    solve(0,1,1,1);
    while(T--){
        scanf("%d",&k);
        for(int i = 0; i < (1 << k); i++){
            scanf("%d",&a[i]);
        }
        ll sum = 0;
        for(int i = 0; i < maxn; i++){
            sum += (ll)ans[i] * a[i&(1<<k)-1];
        }
        printf("%lld\n",sum);
    }
    return 0;
}

此外这题还以一种方法来求本原勾股数组,真是打开眼界

利用这个数来生成Tree of primitive Pythagorean triples

也是递归,其实就是给了三个矩阵,每个本原勾股数组乘这三个 矩阵都会生成三个新的本原勾股数组,而且这样递归下去不会重复,那么让根节点为(3,4,5),一直往下递归求即可,其实就是个三叉树,当然了本地必然还是跑不出来的,别想了1e9呢

#include<bits/stdc++.h>
using namespace std;
const int LMT = 1e9;
long long cnt = 0;
const int N = 1<<17;
const int MOD = N - 1;
int dig[N], a[N], T, k;
//核心算法
void solve(long long a, long long b, long long c)
{
    if(c > LMT) return;
    dig[ max(a, b)&MOD ] ++;
    long long aa = a<<1;
    long long bb = b<<1;
    long long cc = c<<1;
    solve(a-bb+cc, aa-b+cc, aa-bb+cc+c);
    //solve(a-(b<<1)+(c<<1), (a<<1)-b+(c<<1), (a<<1)-(b<<1)+(c<<1)+c);

    solve(a+bb+cc, aa+b+cc, aa+bb+cc+c);
    //solve(a+(b<<1)+(c<<1), (a<<1)+b+(c<<1), (a<<1)+(b<<1)+(c<<1)+c);

    solve(bb+cc-a, b+cc-aa, bb+cc+c-aa);
    //solve(-a+(b<<1)+(c<<1), -(a<<1)+b+(c<<1), -(a<<1)+(b<<1)+(c<<1)+c);
}
int main()
{
    solve(3, 4, 5);
    scanf("%d", &T);
    while(T-- && scanf("%d", &k)!=EOF)
    {
        int mod = (1<<k);
        for(int i=0;i<mod;i++)
            scanf("%d", &a[i]);
        long long ans = 0;
        for(int i=0, j=0;i<N;i++,j++)
        {
            if(j == mod)    j = 0;
            ans += dig[i] * a[j];   
        }
        printf("%lld\n", ans);
    }
}

猜你喜欢

转载自blog.csdn.net/codeswarrior/article/details/82703234