[BZOJ 1041] 圓上的整點

Description
  求一個給定的圓(x2+y2=r2 ),在圓周上有多少個點的座標是整數
Input
  只有一個正整數n ,n2000000000
Output
  整點個數
Sample Input
4
Sample Output
4

初見這道題時候果斷不會。無奈上網找題解。
我在網上找到的做法是這樣的:http://hzwer.com/1457.html
超級麻煩的推導過程讓沒有耐心的我直接放棄這道題。

後來胡亂翻數論書,偶然看見一個神奇的定理,它大概長這樣:

n1 ,則不定方程x2+y2=n 的解數爲:

N(p)=4d|ph(d)
其中函數h(d) 定義爲:
(2)h(d)={1,d=10,2|d(1)(d1)/2,else

至於這個定理的證明,中心思想就是把不定方程 x2+y2=n 的非負本原解和二次同餘方程 s21(modn) 的解一一對應起來。證明細節從略。

再仔細看一看我們可以發現下面幾件事:

  • d=2q·pq0pN(d)=N(p)
  • h(d)=14|d1h(d)=14|d3
  • N(d)d4143

也就是說只需要分別求出 d 除4餘1與除4餘3的因子個數
首先明確暴力搜索因子肯定會T,因此很容易想到把 r 標準分解,然後把所有素因子按照mod4分類,在注意到在mod4下有 3×3=1 即可算出所需要的結果。

下一步的想法就是:
如果r2 的標準分解形式爲r2=Πpi2αiΠqi2βi ,其中4|pi14|qi3 ,並記A=Πpi2αi,B=Πqi2βi ,那麼r2 的因子一定可表示爲B 的某一因子與A 的某一因子的乘積,又由於A 的所有因子均滿足mod4=1,由乘法原理,r2 的除4餘1因子個數等於B 的除4餘1因子個數乘上A 的因子個數

下面給出dfs的實現方法

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long LL;

int prime[2][2340],cnt[2];
//prime[0]記錄mod4=1的素數,用cnt[0]記錄個數,prime[1]和cnt[1]同理
int po[2340];
//po[i]記錄prime[1][i]在r中的冪次
int tmp,ans1=1,ans;
bool vis[44725];
LL r;

void dfs(int u,int mod)  //當前討論prime[1][u],當前餘數爲mod
{
    if(u>cnt[1])
    {
        if(mod==1)  ans++;
        else  ans--;
        return;
    }
    dfs(u+1,mod);
    for(int i=1;i<=po[u];i++)
    {
        if(i&1)  dfs(u+1,mod^2);
        else  dfs(u+1,mod);
    }
}
void init()
{
    int mx=sqrt(r);
    for(int i=3;i<=mx;i+=2)  if(!vis[i])
    {
        if((i>>1)&1)  prime[1][++cnt[1]]=i;
        else  prime[0][++cnt[0]]=i;
        for(int j=i*2;j<=mx;j+=i)  vis[j]=1;
    }
    for(int i=1;i<=cnt[0];i++,tmp=0)//mod4=1的素數的冪次後期不需要,因此不用存
    {
        while(!(r%prime[0][i]))  tmp++,r/=prime[0][i];
        ans1*=1+(tmp<<1);
    }
    for(int i=1;i<=cnt[1];i++)
    {
        while(!(r%prime[1][i]))  po[i]++,r/=prime[1][i];
        po[i]<<=1;
    }
    //此時r有三種情況1,mod4=1的素數,mod4=3的素數
    if((r>>1)&1)  po[++cnt[1]]=2;
    else  if(r>1)  ans1*=3;
}
void solve()
{
    dfs(1,1);
    printf("%d",ans*ans1<<2);
}

int main()
{
    scanf("%lld",&r);
    while(!(r&1))  r>>=1;
    init();  solve();
    return 0;
}

代碼意義很明確了吧?那就不做過多說明了。
評測結果:運行時間8ms,比上邊給鏈接的那個(188ms)快好多。

本來到這就結束了。但我翻了翻評測記錄,發現有若干4ms的程序以及幾個0ms的。我當時就不服了。
我們發現其實po數組大部分數是0,於是乎就有了一個小小的優化

void solve()
{
    sort(po+1,po+cnt[1]+1,greater<int>());
    while(!po[cnt[1]])  cnt[1]--;
    dfs(1,1);
    printf("%d",ans*ans1<<2);
}

運行時間從8ms提高到4ms
後邊我就真的沒轍了。然後經由一位學長提醒,我發現這題能dp,而且是入門dp

簡而言之,用dp[0][i] 表示使用q1,q2,···,qi 能產生的B 的mod4=1的因數個數,dp[1][i] 表示使用q1,q2,···,qi 能產生的B 的mod4=3的因數個數,於是有狀態轉移方程:

dp[1][i]=dp[0][i-1]*(po[i]+1)/2+dp[1][i-1]*(po[i]/2+1);
dp[0][i]=dp[0][i-1]*(po[i]/2+1)+dp[1][i-1]*(po[i]+1)/2;

這裏放一個給出這種思路的學長的blog的鏈接

運行時間:0ms


Update On 2017.10.9

這一定是這道題的最優解法
——纔怪。

事實上在今年的中國東南數學聯賽之前我是這麼認爲的。
不過後來,在東南聯賽的考場上,我意外地發現,東南聯賽高一組的D1T3本質上就是這個定理。
既然它能出成數競題(貌似還是個IMOSL),那麼就一定是有比DP更優的解法。
我們再來分析下這個DP的式子。這次,我們用數列語言表述它:

記數列{an=dp[0][n]},{bn=dp[1][n]},{αn=po[n]}, 那麼有

ai=ai1([αi2]+1)+bi1[αi+12]

bi=ai1[αi+12]+bi1([αi2]+1)

上述兩式相減,得到
aibi=(ai1bi1)([αi2]+1[αi+12])

由題目限制,αi 始終爲偶數,因此上式等號右邊第二項的值恆爲1,於是就有anbn=1

再結合前面的全部討論,我們不難得出結論,所求答案就是A 的因數個數的4倍。於是我們就可以在分解素因子的同時解決這個問題。
時間複雜度O(n)

附:神仙LCA的做法。這裏用到了代數數論中的二次代數數的相關知識,雖然用到了不同的道具,但所得的結果和我這個初等方法一致。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章