1. 程式人生 > 其它 >【luogu P6860】象棋與馬(數學)(杜教篩)

【luogu P6860】象棋與馬(數學)(杜教篩)

給你一個數 n,然後問你有多少個 a<=n,b<=n 滿足在一個二維網格上從源點出發每次走一個 a*b 的矩陣,能走到圖上的所有整點。

象棋與馬

題目連結:luogu P6860

題目大意

給你一個數 n,然後問你有多少個 a<=n,b<=n 滿足在一個二維網格上從源點出發每次走一個 a*b 的矩陣,能走到圖上的所有整點。

思路

首先考慮怎樣的點對是滿足條件的。

首先我們考慮如果它能從 \((0,0)\) 走到 \((0,1)\),那它就能走到所有點。(因為你可以一步一步走)

那接著考慮怎麼搞,首先順序無關,所以我們可以把它分成兩段位移:\((\pm x,\pm y)\)\((\pm y,\pm x)\)

然後第一段的整體位移可以表示成 \((2ax,2by)\) 或者 \((2ax+x,2by+y)\),第二段就是 \((2cy,2dx)\)

\((2cy+y,2dx+x)\)

考慮列出四種可能的方法:
\(\left\{\begin{matrix} 2ax=2cy \\ 2by=2dx+1 \end{matrix}\right.\)

\(\left\{\begin{matrix} 2ax+x=2cy \\ 2by+y=2dx+1 \end{matrix}\right.\)

\(\left\{\begin{matrix} 2ax=2cy+y \\ 2by=2dx+x+1 \end{matrix}\right.\)

\(\left\{\begin{matrix} 2ax+x=2cy+y \\ 2by+y=2dx+x+1 \end{matrix}\right.\)

然後解四個方程,第一是 \(2(ax-cy)=0,2(by-dx)=1\),由於 \(x,y\) 互質,所以 \(by-dx\) 會可以是任意整數(設為 \(k\) 下同):
\(2k=0,2k=1\),無解

第二個是跟上面差不多,就是 \(2k+x=0,2k+y=1\),那就是 \(x\) 是偶數,\(y\) 是奇數。

第三個就是 \(2k-y=0,2k-x=1\),那就是 \(y\) 是偶數,\(x\) 是奇數。

第四個是 \(2k+x-y=0,2k+y-x=1\),那就是無解。

那綜上,我們可以總結出合法的情況:
\(\gcd(i,j)=1\),且 \(i+j\) 是奇數。


然後考慮如何繼續求,那 \(i+j\)

是奇數,\(i-j\) 自然也是。
然後根據輾轉相減法:\(\gcd(i,j)=1\) 就有 \(\gcd(i,i-j)=1\)
你考慮設 \(i>j\)(到時算出來答案乘 \(2\),因為顯然 \(i=j\) 是不成立)

\(\sum\limits_{i=1}^n\sum\limits_{j=1}^{i-1}2[(gcd(i,j)=1)\&((i-j)\bmod 2=1)]\)

然後發現如果不看奇偶它就是 \(\sum\limits_{i=1}^n\varphi(i)\),那你考慮有了右邊之後會怎麼樣。
考慮這個新的值是 \(w(i)\)
(原來的式子變成:\(\sum\limits_{i=1}^n2w(i)\)

考慮從 \(\varphi(i)\) 的性質考慮,如果 \(i\) 是偶數,那所有偶數跟它都不是互質的,那這個操作就不會影響,所以 \(w(i)=\varphi(i)\)
如果 \(i\) 是奇數,那我們考慮一個東西,就是這個互質的對稱性。
什麼意思呢,就是如果 \(i,j\) 互質(\(i>j\)),那麼 \(i,i-j\) 也互質。

那我們就其實可以看做是 \(w(i)=\dfrac{\varphi(i)}{2}\),當然你會發現 \(w(1)\) 要特判掉。(應該是 \(0\) 而不是 \(\dfrac{1}{2}\)
(其實可以不特判,然後到時答案減 \(1\) 即可,減一是因為後面乘 \(2\)

然後你就可以列出 \(w(i)=\left\{\begin{matrix}\varphi(i)&i\mod2=0 \\ \dfrac{\varphi(i)}{2}&i\mod2=1\end{matrix}\right.\)

然後把 \(2\) 放進去:
\(2w(i)=\left\{\begin{matrix}2\varphi(i)&i\mod2=0 \\ \varphi(i)&i\mod2=1\end{matrix}\right.\)
\(2w(i)=\varphi(i)+\left\{\begin{matrix}\varphi(i)&i\mod2=0 \\ 0&i\mod2=1\end{matrix}\right.\)

然後放回原來的式子:
\(\sum\limits_{i=1}^n2w(i)\)
\(\sum\limits_{i=1}^n\varphi(i)+\sum\limits_{i=1}^n\left\{\begin{matrix}\varphi(i)&i\mod2=0 \\ 0&i\mod2=1\end{matrix}\right.\)

那我們再看右邊的部分,那隻看有值的,就是偶數的。
然後再從遞推式求的方式來看 \(\dfrac{i}{2}\),如果它是偶數那返回的就是 \(\varphi(\dfrac{i}{2})\),否則就是 \(\dfrac{\varphi(\frac{i}{2})}{2}\),你會發現它就是 \(w(\left\lfloor\dfrac{i}{2}\right\rfloor)\) 的值。

然後就有:
\(\sum\limits_{i=1}^nw(i)=\sum\limits_{i=1}^n\varphi(i)+\sum\limits_{i=1}^{\left\lfloor\frac{i}{2}\right\rfloor}w(i)\)
左邊的用杜教篩,右邊的遞迴下去就好了。

複雜度是 \(O(n^{\frac{2}{3}}\log n)\),看似很危,但其實跑不滿(每次遞迴的 \(n\) 越來越小)可以過。

程式碼

#include<map>
#include<cstdio>
#define ll unsigned long long

using namespace std;

//const ll Maxn = 21544346.900318837217592935665195;
const ll Maxn = 22000000;
int T;
ll n, ans, phi[Maxn + 1], prime[Maxn / 10];
bool np[Maxn + 1];
map <int, ll> ans_phi;

void init() {//phi 的預處理
	phi[1] = 1;
	for (int i = 2; i <= Maxn; i++) {
		if (!np[i]) {
			phi[i] = i - 1; prime[++prime[0]] = i;
		}
		for (int j = 1; j <= prime[0] && i * prime[j] <= Maxn; j++) {
			if (i % prime[j]) phi[i * prime[j]] = phi[i] * (prime[j] - 1), np[i * prime[j]] = 1;
				else {
					phi[i * prime[j]] = phi[i] * prime[j], np[i * prime[j]] = 1;
					break;
				}
		}
	}
	for (int i = 1; i <= Maxn; i++) phi[i] += phi[i - 1];
} 

ll get_phi(ll n) {//杜教篩
	if (n < Maxn) return phi[n];
	if (ans_phi[n]) return ans_phi[n];
	ll re = (n & 1llu) ? (1llu + n) / 2llu * n : n / 2llu * (1llu + n);
	for (ll l = 2, r; l <= n; l = r + 1) {
		r = n / (n / l);
		re -= (r - l + 1) * get_phi(n / l);
	}
	return ans_phi[n] = re;
}

ll F(ll n) {
	if (!n) return 0;
	return get_phi(n) + F(n / 2);
}

int main() {
	init();
	
	scanf("%d", &T);
	while (T--) {
		scanf("%llu", &n);
		printf("%llu\n", F(n) - 1llu);
	}
	
	return 0;
}