[JLOI2015] 有意義的字串 題解

Supor__Shoop發表於2024-09-27

拿到題目,我們首先分析一下這個奇怪的式子:

\[\lfloor(\frac{b+\sqrt{d}}{2})^n \rfloor ~\text{mod}~p \]

重點肯定是在裡面的那個式子裡面,最顯眼的肯定也就是那個 \(\sqrt{d}\),根據整體形式,我們可以聯絡一元二次方程的求根公式 \(x=-\dfrac{-b \pm \sqrt{b^2-4ac}}{2a}\),這裡也是一個根號,並且和我們要求的極為相似。於是乎我們嘗試著將原式轉換成一個方程的根。設原方程為 \(Ax^2+Bx+C=0\),根據對照可以列出方程:

\[\begin{cases} 2A=2\\ -B=b \\B^2-4AC=d \end{cases} \]

解得:

\[\begin{cases} A=1\\ B=-b \\ C=\dfrac{b^2-d}{4} \end{cases} \]

因此原方程轉變成了:\(x^2-bx+\dfrac{b^2-d}{4}=0\)。由於每一項的次數呈遞減,所以我們考慮移項,看看是否會形成一個轉移式子:

\[x^2=bx-\dfrac{b^2-d}{4} \]

這個非常明顯,是個標準的轉移式子。這個時候就可以想到樸素 DP 大法,按照次數設計狀態,然後根據前面的次數進行轉移。

但原方程會有兩個根,就是 \(\dfrac{b\pm \sqrt{d}}{2}\)。所以直接的轉移會包括 \(\dfrac{b-\sqrt{d}}{2}\) 帶來的影響值。那麼我們需要將 DP 進行合理的定義。設 \(dp_n\) 表示 \((\dfrac{b+\sqrt{d}}{2})^n+(\dfrac{b-\sqrt{d}}{2})^n\) 的值,這個時候我們只需要拿 \(dp_n\) 減去 \((\dfrac{b-\sqrt{d}}{2})^n\) 的影響值就可以了。

輕鬆寫出轉移式子:

\[dp_{i}=b\times dp_{i-1}-\dfrac{b^2-d}{4}\times dp_{i-2} \]

但是由於這個次數超出了 int 範圍,所以考慮進行最佳化,這種對極大 \(n\) 的最佳化,我們不難想到矩陣快速冪最佳化。我們把轉移方程等號右邊涉及到的變數提取出來:\(dp_{i-1}\)\(dp_{i-2}\),因此初始矩陣是一個 \(1\times 2\) 的矩陣 ,而對應的常數項就是 \(b\)\(\dfrac{b^2-d}{4}\),所以轉移矩陣是一個 \(2\times 2\) 的矩陣。初始矩陣 \(st\) 和轉移矩陣 \(a\) 如下:

\[st= \left( \begin{matrix} dp_{i-1} & dp_{i-2} \end{matrix} \right) \]

\[a= \left( \begin{matrix} b & 1 \\ -\dfrac{b^2-d}{4} & 0 \end{matrix} \right) \]

\(st\) 的初始化就是 \(n=1\)\(n=0\) 的情況。

然後我們突然發現一個問題,我們減去的 \((\dfrac{b-\sqrt{d}}{2})^n\) 是一個帶根號的式子,我們該如何求出這個向下取整的值呢?

就在一籌莫展之際,那個亮眼的資料範圍映入眼簾:\(b^2\leq d<(b+1)^2\)。我們嘗試將 \(\sqrt{d}\) 帶入進去,\(b\leq \sqrt{d} < b+1\)。所以 \(-1< \dfrac{b-\sqrt{d}}{4}\leq 0\)

由於 \(|\dfrac{b-\sqrt{d}}{4}|<1\)\(n\) 是個非負整數,所以 \(0\leq |(\dfrac{b-\sqrt{d}}{2})^n|<1\),此時進行分類討論:

  • 如果 \(b-\sqrt{d}=0\),則 \((\dfrac{b-\sqrt{d}}{2})^n=0\),向下取整為 \(0\)

  • 如果 \(b-\sqrt{d}<0\),且 \(2\mid n\),此時原式會變成正數,則 \(0<(\dfrac{b-\sqrt{d}}{2})^n<1\),向下取整為 \(0\)

  • 如果 \(b-\sqrt{d}<0\),且 \(2\nmid n\),此時原式依然是負數,則 \(-1<(\dfrac{b-\sqrt{d}}{2})^n<0\),向下取整為 \(-1\)

綜上所述,\((\dfrac{b-\sqrt{d}}{2})^n\) 的影響值無外乎就是 \(0\) 或者 \(-1\),計算的時候,我們判斷第三種情況就可以了。

最後由於模數 \(p=7528443412579576937 \approx 7\times 10^{18}\),計算過程很有可能爆 long long,所以我們可以開一個老祖宗 __int128,寫個快讀快寫,就可以了過了。

程式碼如下:

#include<bits/stdc++.h>
#define int __int128//懶 
using namespace std;
const int MAXN=83;
const int MOD=7528443412579576937;
int b,d,n;
void read(int &x)
{
	x=0;
	short flag=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')	flag=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	x*=flag;
}
int quick_mul(int x,int y)//龜速乘,可能沒有必要但是就是想寫 
{
	int res=0;
	while(y)
	{
		if(y&1)	res=res+x,res%=MOD;
		x=x+x,x%=MOD;
		y>>=1;
	}
	return res;
}
void mul(int f[3],int a[3][3])//轉移模板 
{
    int c[3];
    memset(c,0,sizeof(c));
    for(int j=1;j<=2;j++)
    {
        for(int k=1;k<=2;k++)    c[j]=(c[j]+quick_mul(f[k],a[k][j]))%MOD;
    }
    memcpy(f,c,sizeof(c));
}
void mulself(int a[3][3])//矩陣自乘模板 
{
    int c[3][3];
    memset(c,0,sizeof(c));
    for(int i=1;i<=2;i++)
    {
        for(int j=1;j<=2;j++)
        {
            for(int k=1;k<=2;k++)    c[i][j]=(c[i][j]+quick_mul(a[i][k],a[k][j]))%MOD;
        }
    }
    memcpy(a,c,sizeof(c));
}
void write(int x)
{
	if(x<10)
	{
		putchar(x+'0');
		return;
	}
	write(x/10);
	putchar(x%10+'0');
}
signed main()
{
	read(b),read(d),read(n);
	if(!n)//特判 
	{
		puts("1");
		return 0;
	}
	if(n==1)//特判 
	{
		long long B=b,D=d;//玄學除錯 
		cout<<((B+sqrt(D)))/2;
		return 0;
	}
	int f[3];
	memset(f,0,sizeof(f));
	int a[3][3];
	memset(a,0,sizeof(a));
	a[1][1]=b,a[1][2]=1,a[2][1]=-(b*b-d)/4;
	f[2]=b,f[1]=(b*b+d)/2;//構造兩個矩陣 
	n-=2;
	int temp=n;//n和n-2的奇偶性相同,所以提前儲存n-2不會錯 
	while(n)//快速冪模板 
	{
		if(n&1)	mul(f,a);
		mulself(a);
		n>>=1;
	}
	if(b*b!=d&&!(temp&1))	f[1]--;//特判第三種情況 
	write(f[1]);//輸出 
	return 0;//華麗收場 
}

相關文章