算法馬拉松35 E 數論只會Gcd - 類歐幾里得 - Stern-Brocot Tree - 莫比烏斯反演

題目傳送門

  傳送門

  這個官方題解除了講了個結論,感覺啥都沒說,不知道是因爲我太菜了,還是因爲它真的啥都沒說。

  如果 $x \geqslant y$,顯然 gcd(x, y) 只會被調用一次。

  否則考慮每次操作前的數對應該是 $(y, y + kx)$。這樣仍然不好處理。考慮忽略掉達到的 $a < b$ 的狀態,那麼每次的 $k \geqslant 1$。那麼當較大數加上較小數的時候對應將 $k$ 加上 1,對應交換兩邊的數,然後將 $k$ 加上1。特別地,第一次操作不能做大加上小,因爲第一次操作的時候沒有 $k$。

  顯然每次操作中,數對可以表示爲 $(ax + by, cx + dy)$。那麼一次加操作會得到 $(a + c) x , (c + d) y$,你發現這個東西和 SBT 的構造有點像。考慮把這個操作對應到 SBT 上。在兩個相鄰分數 $a, b$ 中插入一個分數 $c$ 可以得到新的兩對 $a, c$ 和 $(c, b)$,分別可以看右加上左邊以及左邊加上右邊。

  暫時不考慮 $m$ 的限制,我們來簡單說明一下滿足除了初始的數對一個數對可以對應 SBT 上某一層的一對相鄰分數。考慮給出和上轉化後的相同的生成方式。

  考慮第 $k$ 層中一對存在對應關係的相鄰分數 $(p, q)$。

  如果 $p < q$,那麼在樹上的情況上是

 

 

  假設在 $p, q$ 間插入的分數爲 $t$,根據 SBT 的構造方式可知 $q, t$ 是第 $(k + 1)$ 層的相鄰分數 $t, p$ 是第 $(k + 1)$ 層的相鄰分數。它們分別對應右加上左以及左加上右。當 $q < p$ 的時候是類似的。

  對於一個真分數 $\frac{a}{b}$,$xa + yb$ 的值總是比相應的它生成的兩個分數的 $x'a + y'b$ 小 。一對相鄰分數一定滿足一個是另一個的祖先,這個不難使用歸納法證明。

  現在考慮加入 $m$ 的限制,那麼真分數 $\frac{x}{y}$ 滿足條件當且僅當 $x \leqslant y$ 以及 $xa + yb \leqslant m$,並且每一個滿足條件的小於 $1$ 的真分數對應 $4$ 個滿足條件的數對,特別地,1 如果合法只會對應 2 個滿足條件的數對。

  前一個條件是因爲第一次只能大加上小,第二個是因爲題目限制。充分性由 SBT 構造過程和上面轉化給出。

  那剩下的問題就非常傻逼了:

$$
\begin{align}
\sum_{i = 1}^{m} \sum_{j = 1}^{m} [i \leqslant j][(i, j) = 1][xi + yj \leqslant m]
\end{align}
$$

  基礎莫比烏斯反演 & 類歐幾里得即可計算。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

#define ll long long

ll ceil(ll a, ll b) {
	return (a < 0) ? ((a - b + 1) / b) : (a / b);
}
 
ll calc(ll a, ll b, ll c, ll n) {
	if (!n) {
		return 0;
	}
	if (!a) {
		return ceil(b, c) * n;
	}
	if (b < 0 || b >= c || a < 0 || a >= c) {
		ll ka = ceil(a, c), kb = ceil(b, c);
		ll tmp = ka * ((n * (n - 1)) >> 1) + kb * n;
		return calc(a - ka * c, b - kb * c, c, n) + tmp;
	}
	ll m = ((n - 1) * a + b) / c;
	return n * m - calc(c, c - b + a - 1, a, m);
}

const int C = 1e6 + 5;
const int D = 4e4 + 5;

int pri[C];
int mu[C], smu[C];

void Euler(int n) {
	static bitset<C> vis;
	int num = 0;
	mu[1] = 1;
	for (int i = 2; i <= n; i++) {
		if (!vis.test(i)) {
			pri[num++] = i;
			mu[i] = -1;
		}
		for (int *p = pri, *_ = pri + num, x; p != _ && (x = *p * i) <= n; p++) {
			vis.set(x);
			if (i % *p) {
				mu[x] = -mu[i];
			} else{
				mu[x] = 0;
				break;
			}
		}
	}
	for (int i = 1; i <= n; i++)
		smu[i] = smu[i - 1] + mu[i];
}

int T, N;

int smu1[D];
boolean vis[D];
int S(int n) {
	if (n <= 1000000)
		return smu[n];
	if (vis[N / n])
		return smu1[N / n];
	int &rt = smu1[N / n];
	rt = 1;
	vis[N / n] = true;
	for (int i = 2, j; i <= n; i = j + 1) {
		j = n / (n / i);
		rt -= S(n / i) * (j - i + 1);
	}
	return rt;
}

int main() {
	scanf("%d%d", &T, &N);
	Euler(1000000);
	int x, y;
	while (T--) {
		scanf("%d%d", &x, &y);
		if (x <= y) {
			puts("1");
			continue;
		}
		ll ans = 0;
		for (int i = 1, j; i <= N / (x + y); i = j + 1) {
			j = N / (N / i);
			ans += (S(j) - S(i - 1)) * calc(-x - y, N / i - x - y, x, N / (i * (x + y)));
		}
		ans = ((ans << 1) + 1 + (x + y <= N)) << 1;
		printf("%lld\n", ans);
	}
	return 0;
} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章