P6860 象棋与马 杜教筛
P6860 象棋与马 杜教筛
题意
有一个无限大的棋盘,有一个马最初在\((0,0)\),它的每步可以走一个\(a \times b\)的矩形,即能够走到\((x \pm a,y \pm b),或者(x \pm b,y \pm a)\)
若马通过上述移动方式可以达到棋盘中任意一点,那么\(p(a,b) = 1\),否则\(p(a,b) = 0\)
给出\(T\)组询问,每组询问会给出一个正整数\(n\),求出
\[(\sum_{a=1}^n\sum_{b=1}^np(a,b))mod2^{64}
\]
\[n \times T \leq 10^{11}\\
T \leq 5
\]
分析
走到棋盘的充要条件是能够走到\((1,0)\)
如果不能走到\((1,0)\),显然无法走到棋盘的任一点,如果能走到\((1,0)\),显然可以走到棋盘的任意位置
由于只有本质不同的4种走法
- \((a,b)\)
- \((b,a)\)
- \((-a,b)\)
- \((-b,a)\)
走到\((1,0)\)的充要条件是
\[x_1 \cdot a + x_2 \cdot b - y_1 \cdot a - y_2 \cdot b = 1\\
x_1 \cdot b + x_2 \cdot a + y_1 \cdot b + y_2 \cdot a = 0
\]
上下相加,有
\[(x_1+ x_2 - (y_1 - y_2)) a + (x_1 + x_2 + (y_1 - y_2))b = 1
\]
即
\[(x - y)a + (x + y) b = 1
\]
可见
\[(a,b) = 1\\
\]
由于$(x + y) \(与\)(x - y)$必奇偶性同,且必全为奇数,若是偶数,显然右边不等于1
可以得出a,b奇偶性不同,若相同,右边不等于0
下面进行对答案讨论
若\(a\)为偶数,只需要求出为奇数且与\(a\)互质的数,答案显然是\(\phi(a)\)
若\(a\)为奇数,只需要求出为偶数且与\(a\)互质的数,答案为\(\frac{\phi(a)}{2}\),这是因为,\(a\)为奇数时,与它互质的数总是成对出现的
这是发现答案就是
\[ans = \sum\phi(i) + \sum_{i 为偶数}\phi(i)
\]
由于\(n\)达到1e11,这就成为了杜教筛的模板题
至于后面那一坨,考虑用递归式处理
\[S(n) = \sum \phi(i)\\
ans(n) = S(n) + ans(n / 2)
\]
用到的欧拉函数性质
\[a \% 2 == 1,\phi(2a) = \phi(a)\\
a \% 2== 0,\phi(2a) = 2\phi(a)
\]
代码
map<ull, ull> Sphi;
const int M = 1e7 + 5;
ull prime[M], vis[M], phi[M], s[M], n, m;
void init() {
phi[1] = 1;
for (re int i = 2; i <= M - 5; i++) {
if (!vis[i]) {
vis[i] = i;
prime[++m] = i;
phi[i] = i - 1;
}
for (re int j = 1; j <= m && prime[j] *i <= M - 5; j++) {
if (prime[j] > vis[i]) break;
vis[i * prime[j]] = prime[j];
phi[i * prime[j]] = phi[i] * (i % prime[j] ? prime[j] - 1 : prime[j]);
}
}
for (re int i = 1; i <= M - 5; i++) {
if (i & 1) s[i] = phi[i << 1] / 2;
else s[i] = phi[i];
s[i] += s[i - 1];
phi[i] += phi[i - 1];
}
}
ull getphi(ull n) {
if (n <= M - 5)return phi[n];
if (Sphi[n]) return Sphi[n];
ull res = 0;
if (n % 2 == 0) res = n / 2 * (n + 1);
else res = (n + 1) / 2 * n;
for (ull l = 2, r; l <= n; l = r + 1) {
r = n / (n / l);
res -= (r - l + 1) * getphi(n / l);
}
return Sphi[n] = res;
}
ull S(ull n) {
if (n <= 1) return 0;
return getphi(n) + S(n / 2);
}
int main() {
init();
int T = readint();
while (T--) {
n = readull();
printf("%llu\n", S(n));
}
}