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 thatx<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

題意:

給出2kai ,求出所有滿足x,y,zx<y<z<1e9ay%2k 是多少

分析:

這題是個很迷的題,各種神奇

首先學習到了對於取模2的冪次來說,可以進行交換(我沒找到相關定理,只是根據題解推出來的,要不然這題不可能對),什麼意思呢,就是比如一個數n ,計算n%2k1%2k2 ,如果k1>k2 ,那麼n%2k1%2k2=n%2k2 ,舉個具體的例子來說9%8%2=1=9%2=1

這個結論貌似只對取模2的冪有效,對於一般數的取模是肯定不對的(我沒找到相關定理解釋,但是猜測是對的,因爲這道題對了,我也是看着他們的題解)

下面先說一下這道題,很明顯我們需要得到1e9內的本原勾股數組,並且我們知道本原勾股數組公式爲:

(2mn,m2n2,m2+n2)

mn 互質,mn 有一奇一偶,即mn 是奇數

維基百科原話這樣說:

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=m2n2, b=2mn, c=m2+n2

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的規模,而每次取模最大217 ,所以預處理的時候就直接取模217 好了,然後這樣預處理出每個y取模後對應下標出現了幾次

在得到每個數列a的時候,遍歷1到217 ,看每個y的貢獻次數,然後在乘上這個實際取模2k 下標的那個a數列中的值,求和即可

而且這裏取模是用位運算做的,結論取模2的冪次的數,相當於和這個數-1按位與即
mod  2k =n &((1<<k)1)

最後一個神奇的地方,網上有人說法裏數列Stern-Brocot tree構造的時間複雜度是O(n2) 的,不知道怎麼算,感覺這個複雜度恨不能理解,怎麼不超時呢?我用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);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章