Description
求一個給定的圓( ),在圓周上有多少個點的座標是整數
Input
只有一個正整數 ,
Output
整點個數
Sample Input
4
Sample Output
4
初見這道題時候果斷不會。無奈上網找題解。
我在網上找到的做法是這樣的:http://hzwer.com/1457.html
超級麻煩的推導過程讓沒有耐心的我直接放棄這道題。
後來胡亂翻數論書,偶然看見一個神奇的定理,它大概長這樣:
設 ,則不定方程 的解數爲:
至於這個定理的證明,中心思想就是把不定方程 的非負本原解和二次同餘方程 的解一一對應起來。證明細節從略。
再仔細看一看我們可以發現下面幾件事:
也就是說只需要分別求出 除4餘1與除4餘3的因子個數
首先明確暴力搜索因子肯定會T,因此很容易想到把 標準分解,然後把所有素因子按照mod4分類,在注意到在mod4下有 即可算出所需要的結果。
下一步的想法就是:
如果 的標準分解形式爲 ,其中 ,並記 ,那麼 的因子一定可表示爲 的某一因子與 的某一因子的乘積,又由於 的所有因子均滿足mod4=1,由乘法原理, 的除4餘1因子個數等於 的除4餘1因子個數乘上 的因子個數
下面給出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
簡而言之,用 表示使用 能產生的 的mod4=1的因數個數, 表示使用 能產生的 的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的式子。這次,我們用數列語言表述它:
記數列 那麼有
上述兩式相減,得到
由題目限制, 始終爲偶數,因此上式等號右邊第二項的值恆爲1,於是就有
再結合前面的全部討論,我們不難得出結論,所求答案就是 的因數個數的4倍。於是我們就可以在分解素因子的同時解決這個問題。
時間複雜度
附:神仙LCA的做法。這裏用到了代數數論中的二次代數數的相關知識,雖然用到了不同的道具,但所得的結果和我這個初等方法一致。