題意
現有如下方程:$ x \oplus 3x = 2x $
其中 $ \oplus $ 表示按位異或。
共 $ T $ 組資料,每組資料給定正整數 $ n $,任務如下:
- 求出小於等於 $ n $ 的正整數中,有多少個數是該方程的解
- 求出小於等於 $ 2^n $ 的正整數中,有多少個數是該方程的解,輸出 $ mod $ $ 10^9+7 $ 的值。
$ (n \leq 10^{18}, T \leq 1000) $
題解
第一問
方程 $ x \oplus 3x = 2x $ 等價於 $ x \oplus 2x = 3x $ 。
由於 $ x + 2x = 3x $ ,並且“按位異或”相當於“不進位加法“
所以我們可以知道,一個數 $ x $ 是該方程的解的充要條件為:$ x $ 在二進位制表示下,沒有兩個相鄰的 $ 1 $ 。
$ f[i][0/1] $ 表示所有二進位制下長度為 $ i $,且最右邊一位為 $ 0/1 $ 的數中,滿足沒有兩個相鄰的 $ 1 $ 的數的個數。
所以有:
\[
f[i][0] = f[i-1][0] + f[i-1][1]
\]
\[ f[i][1] = f[i-1][0] \]
邊界條件為:$ f[0][0] = 1 $
然後按位統計,算出小於等於 $ n $ 的答案:
- 先讓 $ n = n + 1 $ (因為最後算出的是小於某個數的答案,而這裡要求小於等於)
- 從高到低列舉 $ n $ 的每一個二進位制位 $ a[i] $
- 如果 $ a[i] = 1 $ ,那麼 $ ans = ans + f[i][0] $ 。(此時累加的是第 $ i-1 $ 及之前位和原數匹配時的答案)
- 如果 $ a[i] = 1 $ 且 $ a[i+1] = 1 $ ,退出迴圈。因為此時的字首已經有兩個 $ 1 $ 相鄰,之後的答案都是不合法的。
注意最終答案為 $ ans - 1 $ ,因為不包含 $ 0 $ 這個答案。
複雜度 $ O(logn) $
第二問
$ g[i] $ 表示二進位制下長度為 $ i $ ,滿足沒有兩個相鄰的 $ 1 $ 的數的個數。
由於題目要求區間 $ [1, 2^i] $ 的答案,$ g[i] $ 表示區間 $ [0, 2^i-1] $ 的答案
而 $ 0 $ 和 $ 2^i $ 一定都滿足條件,所以相互抵消掉了。
所以題目所求 $ ans = g[n] $
然後考慮如何求 $ g[i] $ (分兩種情況):
- $ g[i] $ 中以 $ 0 $ 結尾的所有數,相當於在 $ g[i-1] $ 中的所有數末尾添了一個 $ 0 $
- $ g[i] $ 中以 $ 1 $ 結尾的所有數,相當於在 $ g[i-2] $ 中的所有數末尾添了一個 $ 01 $
所以有:
\[
g[i] = g[i-1] + g[i-2]
\]
邊界條件為:$ g[1] = 2, g[2] = 3 $
然後用矩陣快速冪加速轉移即可:
\[
\begin{bmatrix} g[i] & g[i+1] \end{bmatrix} \times \begin{bmatrix} 0 & 1 \\ 1 & 1 \end{bmatrix} = \begin{bmatrix} g[i+1] & g[i+2] \end{bmatrix}
\]
\[ \begin{bmatrix} g[1] & g[2] \end{bmatrix} \times \begin{bmatrix} 0 & 1 \\ 1 & 1 \end{bmatrix}^{n-1} = \begin{bmatrix} g[n] & g[n+1] \end{bmatrix} \]
AC Code
#include <iostream>
#include <stdio.h>
#include <string.h>
#define MAX_L 5
#define MAX_B 70
#define MOD 1000000007
#define int long long
using namespace std;
struct Mat
{
int n,m;
int v[MAX_L][MAX_L];
Mat(int _n,int _m) { n=_n,m=_m,memset(v,0,sizeof(v)); }
Mat() { memset(v,0,sizeof(v)); }
};
int n,t;
int a[MAX_B];
int dp[MAX_B][2];
Mat get_unit(int x)
{
Mat a(x,x);
for(int i=0;i<x;i++) a.v[i][i]=1;
return a;
}
Mat mul(const Mat &a,const Mat &b)
{
Mat c(a.n,b.m);
for(int i=0;i<a.n;i++)
{
for(int j=0;j<b.m;j++)
{
for(int k=0;k<a.m;k++)
{
c.v[i][j]+=a.v[i][k]*b.v[k][j];
c.v[i][j]%=MOD;
}
}
}
return c;
}
Mat pow(Mat a,int k)
{
Mat ans=get_unit(a.n);
while(k>0)
{
if(k&1) ans=mul(ans,a);
a=mul(a,a),k>>=1;
}
return ans;
}
int cal1()
{
int t=n+1,len=0,ans=0;
while(t) a[++len]=(t&1),t>>=1;
a[len+1]=0,dp[0][0]=1;
for(int i=1;i<=len;i++)
{
dp[i][0]=dp[i-1][0]+dp[i-1][1];
dp[i][1]=dp[i-1][0];
}
for(int i=len;i>=1;i--)
{
if(a[i]) ans+=dp[i][0];
if(a[i+1] && a[i]) break;
}
return ans-1;
}
int cal2()
{
Mat a(1,2),b(2,2);
a.v[0][0]=2,a.v[0][1]=3;
b.v[0][1]=b.v[1][0]=b.v[1][1]=1;
return mul(a,pow(b,n-1)).v[0][0];
}
signed main()
{
scanf("%lld",&t);
while(t--)
{
scanf("%lld",&n);
printf("%lld\n",cal1());
printf("%lld\n",cal2());
}
}