数论网课习题解答
Social_Zhao · · 个人记录
数论网课习题解答
前言
数论杀我。
有些题是之前做过的,那就直接口胡式子了。
在题目中可能会用
因为有一定莫反基础,所以我不会再题解里面说很多怎么交换顺序、提什么东西出来,这些应该看得出来吧。
P2303 [SDOI2012] Longge 的问题
问题是求
直接暴力搞能 AC。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int get() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)) { if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)) { x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
int phi(int x) {
int res = x;
for(int i = 2; i * i <= x; i++) {
if(x % i == 0) res = res / i * (i - 1);
while(x % i == 0) x /= i;
}
if(x > 1) res = res / x * (x - 1);
return res;
}
int n;
signed main() {
n = get();
int ans = 0;
for(int i = 1; i * i <= n; i++) {
if(n % i == 0) {
if(i * i != n) ans += i * phi(n / i) + n / i * phi(i);
else ans += i * phi(i);
}
}
cout << ans << endl;
return 0;
}
P2257 YY的GCD
设
预处理后面那个式子(复杂度接近于线性但貌似实际上不是,反正能过就对了),整除分块。
这道题用到了一个经典套路:设
#include<bits/stdc++.h>
#define int long long
using namespace std;
int get() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)) { if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)) { x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
const int N = 1e7 + 5, MaxN = 1e7;
int pri[N], mu[N], vis[N], tot, sum[N];
void get_mu() {
mu[1] = 1;
for(register int i = 2; i <= MaxN; i++) {
if(!vis[i]) mu[i] = -1, pri[++tot] = i;
for(register int j = 1; j <= tot && i * pri[j] <= MaxN; j++) {
vis[i * pri[j]] = 1;
if(i % pri[j] == 0) { mu[i * pri[j]] = 0; break; }
mu[i * pri[j]] = -mu[i];
}
}
for(register int i = 1; i <= tot; i++)
for(register int j = 1; pri[i] * j <= MaxN; j++) sum[pri[i] * j] += mu[j];
for(register int i = 1; i <= MaxN; i++) sum[i] += sum[i - 1];
}
int solve(int n, int m) {
int res = 0;
for(register int i = 1, j; i <= min(n, m); i = j + 1) {
j = min(n / (n / i), m / (m / i));
res += (sum[j] - sum[i - 1]) * (n / i) * (m / i);
}
return res;
}
signed main() {
get_mu();
int T = get();
while(T--) {
int n = get(), m = get();
printf("%lld\n", solve(n, m));
}
return 0;
}
P3327 [SDOI2015]约数个数和
Lemma
证明(口胡)
此证明启发自这篇文章
首先根据唯一分解定理,
设
- 在
i 中选择,需要满足情况c\leq a ,p^c=p^\alpha(\alpha\leq a) 。 - 在
j 中选择,需要满足情况c>a ,p^c=p^a\times p^\beta(\beta \leq b) 。
可以发现不会出现同时在
下面就可以开始推式子了。
发现后面两项实际上就是
#include<bits/stdc++.h>
#define int long long
using namespace std;
int get() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)) { if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)) { x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
const int N = 1e5 + 5, MaxN = 1e5;
int mu[N], d[N], pri[N], tot, num[N], vis[N];
void seive() {
mu[1] = d[1] = 1;
for(int i = 2; i <= MaxN; i++) {
if(!vis[i]) pri[++tot] = i, d[i] = 2, mu[i] = -1, num[i] = 1;
for(int j = 1; j <= tot && i * pri[j] <= MaxN; j++) {
vis[i * pri[j]] = 1;
if(i % pri[j] == 0) {
num[i * pri[j]] = num[i] + 1;
d[i * pri[j]] = d[i]/ num[i * pri[j]] * (num[i * pri[j]] + 1);
mu[i * pri[j]] = 0;
break;
}
num[i * pri[j]] = 1;
d[i * pri[j]] = d[i] * 2;
mu[i * pri[j]] = -mu[i];
}
mu[i] += mu[i - 1], d[i] += d[i - 1];
}
}
int solve(int n, int m) {
int res = 0;
for(int i = 1, j; i <= min(n, m); i = j + 1) {
j = min(n / (n / i), m / (m / i));
res += (mu[j] - mu[i - 1]) * d[n / i] * d[m / i];
}
return res;
}
signed main() {
seive();
int T = get();
while(T--) {
int n = get(), m = get();
printf("%lld\n", solve(n, m));
}
return 0;
}
P4213 【模板】杜教筛(Sum)
详见杜教筛专题文章。
SP4168 SQFREE - Square-free integers
考虑容斥,用总量减去有平方因子的,考虑某一个平方因子的贡献,即:
其中,
- 若
i 本身有平方因子,那么i 的倍数的贡献无需在此计算,\mathrm{sign}(i)=0 ; - 若
i 本身没有平方因子,那么:- 只有一个素因子的应该减去,
\mathrm{sign}(x)=-1 ; - 只有两个素因子的在只有一个素因子的部分已经计算过了两次,应该加回来,
\mathrm{sign}(x)=1 ; - 只有三个素因子的在前面被多加了一次,应该再减去,
\mathrm{sign}(x)=1 ; - 以此类推。
- 只有一个素因子的应该减去,
不难发现,这个
#include<stdio.h>
const int N = 1e7 + 5;
int vis[N], pri[N], tot, mu[N];
void init() {
for(int i = 2; i < N; i++) {
if(!vis[i]) pri[++tot] = i, mu[i] = -1;
for(int j = 1; j <= tot && i * pri[j] < N; j++) {
vis[i * pri[j]] = 1;
if(i % pri[j] == 0) { mu[i * pri[j]] = 0; break; }
mu[i * pri[j]] = -mu[i];
}
}
}
void solve() {
long long n, ans = 0;
scanf("%lld", &n);
ans = n;
for(long long i = 2; i * i <= n; i++) ans = ans + mu[i] * (n / (i * i));
printf("%lld\n", ans);
}
int main() {
init();
int T;
scanf("%d", &T);
while(T--) solve();
return 0;
}
P3312 [SDOI2014]数表
考虑没有
设
考虑将询问离线,每次对于一个可能对当前
#define int long long
using namespace std;
int get() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)) { if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)) { x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
const int N = 1e5 + 5, P = 1ll << 31;
struct Query {
int n, m, a, id;
bool operator <(Query b) const { return a < b.a; }
} q[N];
struct Sigma {
int d, x;
Sigma() {}
Sigma(int a, int b) { d = a, x = b; }
bool operator <(Sigma b) const { return d < b.d; }
} bin[N];
int Q, top, pri[N], tot, vis[N], mu[N], d[N], g[N], ans[N];
namespace BIT {
int sum[N];
int lowbit(int x) { return x & -x; }
void add(int x, int v) { while(x < N) sum[x] = (sum[x] + v) % P, x += lowbit(x); }
int ask(int x) { int rs = 0; while(x) rs = (rs + sum[x]) % P, x -= lowbit(x); return rs; }
int ask(int l, int r) { return (ask(r) - ask(l - 1) + P) % P; }
}
using namespace BIT;
void init() {
mu[1] = d[1] = g[1] = 1;
for(int i = 2; i < N; i++) {
if(!vis[i]) pri[++tot] = i, mu[i] = -1, d[i] = g[i] = i + 1;
for(int j = 1; j <= tot && i * pri[j] < N; j++) {
vis[i * pri[j]] = 1;
if(i % pri[j] == 0) {
mu[i * pri[j]] = 0, g[i * pri[j]] = g[i] * pri[j] + 1, d[i * pri[j]] = d[i] / g[i] * g[i * pri[j]];
break;
}
mu[i * pri[j]] = -mu[i], g[i * pri[j]] = pri[j] + 1, d[i * pri[j]] = d[i] * g[i * pri[j]];
}
}
for(int i = 1; i < N; i++) bin[++top] = Sigma(d[i], i);
sort(bin + 1, bin + 1 + top);
}
void insert(int _d, int _x) {
for(int i = 1; i * _x < N; i++)
add(i * _x, _d * mu[i]);
}
int solve(int n, int m) {
int res = 0;
for(int i = 1, r; i <= min(n, m); i = r + 1) {
r = min(n / (n / i), m / (m / i));
res = (res + (n / i) * (m / i) % P * ask(i, r) % P) % P;
}
return (res + P) % P;
}
signed main() {
Q = get();
for(int i = 1; i <= Q; i++) q[i].n = get(), q[i].m = get(), q[i].a = get(), q[i].id = i;
sort(q + 1, q + 1 + Q);
init();
int maxr = 0;
for(int i = 1; i <= Q; i++) {
while(maxr < top && bin[maxr + 1].d <= q[i].a)
++maxr, insert(bin[maxr].d, bin[maxr].x);
ans[q[i].id] = solve(q[i].n, q[i].m);
}
for(int i = 1; i <= Q; i++) printf("%lld\n", ans[i]);
return 0;
}
P1829 [国家集训队]Crash的数字表格 / JZPTAB
忘了怎么做了
P2480 [SDOI2010]古代猪文
数论全家桶。
首先有要算的东西在指数上,用欧拉定理,记得特判不互质。
肯定得用卢卡斯,但是模数巨大,显然不行,所以考虑将其质因数分解然后 CRT。
别的东西就没啥说的必要了。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int get() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)) { if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)) { x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
const int P = 999911659, N = 114514, Phi = P - 1, p[] = {0, 2, 3, 4679, 35617};
int n, g, ans[5], fac[N], res;
int qpow(int x, int y, int mod) {
int res = 1;
while(y) {
if(y & 1) res = res * x % mod;
x = x * x % mod, y >>= 1;
}
return res;
}
int inv(int x, int mod) { if(!x) return 1; return qpow(x, mod - 2, mod); }
void GetFac(int mod) { fac[0] = 1; for(int i = 1; i <= mod; i++) fac[i] = fac[i - 1] * i % mod; }
int C(int x, int y, int mod) {
if(x < y) return 0;
return fac[x] * inv(fac[y], mod) % mod * inv(fac[x - y], mod) % mod;
}
int Lucas(int x, int y, int mod) {
if(x < y) return 0;
if(!x) return 1;
return Lucas(x / mod, y / mod, mod) * C(x % mod, y % mod, mod) % mod;
}
void CRT() {
for(int i = 1; i <= 4; i++)
res = (res + ans[i] * (Phi / p[i]) % Phi * inv(Phi / p[i], p[i]) % Phi) % Phi;
}
signed main() {
n = get(), g = get();
if(g % P == 0) return printf("0\n"), 0;
for(int t = 1; t <= 4; t++) {
GetFac(p[t]);
for(int i = 1; i * i <= n; i++) {
if(n % i) continue;
ans[t] = (ans[t] + Lucas(n, i, p[t])) % p[t];
if(i * i != n) ans[t] = (ans[t] + Lucas(n, n / i, p[t])) % p[t];
}
}
CRT();
printf("%lld\n", qpow(g, res, P));
return 0;
}
P3768 简单的数学题
详见杜教筛专题。
P1587 [NOI2016]循环之美
Lemma
题目条件等价于求:
证明
暂时不会。
所以就直接开始推式子了(
解释下最后一步:把原来的
如果我们能求出以下两个函数的前缀和,就可以对其进行整除分块:
显然求
问题在于这个
如果
发现后面那东西有点眼熟,它长得很像
那么有:
这样子就可以递归下去,记忆化搜索求解了。边界情况是
复杂度算起来非常迷惑,总之能过就是了。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int get() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)) { if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)) { x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
const int N = 5e6 + 5, K = 2005;
int n, m, _k;
int f[N];
int vis[N], pri[N], tot, mu[N], smu[N];
map<pair<int, int>, int> mem;
int gcd(int x, int y) { return y? gcd(y, x % y) : x; }
void init() {
mu[1] = 1, smu[1] = 1;
for(int i = 2; i < N; i++) {
if(!vis[i]) pri[++tot] = i, mu[i] = -1;
for(int j = 1; j <= tot && i * pri[j] < N; j++) {
vis[i * pri[j]] = 1;
if(i % pri[j] == 0) break;
mu[i * pri[j]] = -mu[i];
}
smu[i] = smu[i - 1] + mu[i];
}
for(int i = 1; i <= _k; i++) f[i] = f[i - 1] + (gcd(i, _k) == 1);
}
int F(int x) {
return f[_k] * (x / _k) + f[x % _k];
}
int S(int x, int k) {
if((k == 1 && x <= N) || (!x)) return smu[x];
if(mem[make_pair(x, k)]) return mem[make_pair(x, k)];
int res = 0;
if(k == 1) {
res=1;
for(int i = 2, j; i <= x; i = j + 1) {
j = x / (x / i);
res -= (j - i + 1) * S(x / i, 1);
}
}
else {
for(int d = 1; d * d <= k; ++d) {
if(k % d) continue;
if(mu[d]) res += S(x / d, d);
if(d * d != k && mu[k / d]) res += S(x / (k / d), k / d);
}
}
return (mem[make_pair(x, k)] = res);
}
signed main() {
n = get(), m = get(), _k = get();
init();
int lst = 0, ans = 0, now;
for(int i = 1, j; i <= min(n, m); i = j + 1) {
j = min(n / (n / i), m / (m / i));
now = S(j, _k);
ans += (now - lst) * (n / i) * F(m / i);
lst = now;
}
printf("%lld\n", ans);
return 0;
}
P4619 [SDOI2018]旧试题
暂时不会。