UESTC2022暑假前(?)集训 数学与几何
数学与几何解题报告
其实还有一个求圆上的整点个数的题,写完忘保存丢掉了。
B-新月之舞 推式子
题意
求
解题思路
$=\sum\limits_{i=1}{n}ni-\sum\limits_{i=1}\sum\limits_{j=1}^{i} \left \lfloor \frac{i}{j} \right \rfloor j $
$=\frac{n2(n+1)}{2}-\sum\limits_{i=1}\sum\limits_{j=1}^{i} \left \lfloor \frac{i}{j} \right \rfloor j $
结论:
证明:考虑中的每个数出现在了几个中,有
因此
根据前面的结论,
只需求出
类似地,我们考虑对答案的贡献,可以得到
=
综上所述,
借助数论分块,可以求出答案。
数论分块是这么一个东西:
出现时,我们可以把个数相加,转化成个区间和相加,每一个区间里是相等的。
例如最简单的
int H(int n) {
int res = 0;
for (int l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
res += (r - l + 1) * (n / l);
}
return res;
}
答案会达到,需要使用int128。
代码
//
// Created by vv123 on 2022/7/4.
//
#include <bits/stdc++.h>
#define int __int128
using namespace std;
inline int read() {
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9'){ x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
inline void print(int x) {
if(x < 0) { putchar('-'); x = -x; }
if(x > 9) print(x / 10);
putchar(x % 10 + '0');
}
int n, ans;
inline int A() {
int res = 0;
for (int l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
res += (l + r) * (r - l + 1) * (n / l) / 2;
}
return res;
}
inline int sumsquare(int x) {
return x * (x + 1) * (x + x + 1) / 6;
}
inline int B() {
int res = 0;
for (int l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
int t = n / l;
res += (sumsquare(r) - sumsquare(l - 1)) * t * (t + 1) / 2;
}
return res;
}
void solve() {
n = read();
ans = n * n * (n + 1) / 2 - (n + 1) * A() + B();
print(ans);putchar('\n');
}
signed main() {
int T = read();
while (T--) {
solve();
}
return 0;
}
C-回转数 几何
题意
给出一段由条有向线段组成的有向封闭折线,可能有交叉和重叠,并给出个点,对每个点求折线围绕该点的回转数。
解题思路
-
to-left测试:判断一个点P是否在一条有向直线AB左侧,只需要看是否大于0.
-
回转数:面内闭合曲线逆时针绕过该点的总次数。性质:当回转数为0,点在曲线外部。
-
朴素做法:是计算相邻两边夹角的和,需要使用浮点数。
-
无精度误差的解法:向右作一条射线,与朝上的边相交回转数+1,朝下的边相交回转数-1。
-
朝上的边与射线相交,当且仅当检测点纵坐标介于uv之间,且检测点在uv左侧。另一种情况同理。
-
细节:如果射线恰好经过顶点,不妨定义顶点在射线上方。(只统计产生的答案)
代码
//
// Created by vv123 on 2022/7/18.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 2e4 + 10;
struct point {
int x, y;
point operator-(const point &p) const { return {x - p.x, y - p.y}; }
int operator*(const point &p) const { return x * p.x + y * p.y; }
int operator^(const point &p) const { return x * p.y - p.x * y; }
int quad() const {
if (y > 0 || (y == 0 && x >= 0)) return 1;
return 2;
}//下平面定义为[-180,0),上平面定义为[0,180)
int toleft(const point &p) { //p在这个向量的逆时针方向,则叉积为正
int t = (*this) ^ p;
if (t > 0) return 1;
if (t == 0) return 0;
if (t < 0) return -1;
}
};
bool cmp(const point &a,const point &b) {
if (a.quad() != b.quad()) return a.quad() < b.quad();
else return (a ^ b) > 0;
}
struct line {
point p, v;
int toleft(const point &a) {
return v.toleft(a - p);
}
};
struct polygon {
vector<point> vec;
int nxt(int i) { return (i + 1) % vec.size(); }
pii winding(const point &p) {
int res = 0;
for (int i = 0; i < vec.size(); i++) {
point u = vec[i], v = vec[nxt(i)];
if (((p - u) ^ (p - v)) == 0 && (p - u) * (p - v) <= 0) return {1, 0};
if (u.y == v.y) continue;
line uv = {u, v - u};
if (u.y < v.y && uv.toleft(p) <= 0) continue;
if (u.y > v.y && uv.toleft(p) >= 0) continue;
if (u.y < p.y && v.y >= p.y) res++;
if (u.y >= p.y && v.y < p.y) res--;
}
return {0, res};
};
} poly;
void solve() {
int n, m;
cin >> n;
for (int i = 1; i <= n; i++) {
int x, y;
cin >> x >> y;
poly.vec.push_back({x, y});
}
cin >> m;
while (m--) {
int x, y;
cin >> x >> y;
point p = {x, y};
pii res = poly.winding(p);
if (res.first == 1) puts("EDGE");
else cout << res.second << "\n";
}
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
E-兔儿爷 几何
题意
有三瓶药水,每瓶药水两种溶质的浓度分别为。次询问问能否配制出两种溶质浓度分别为的药水。
解题思路
容易发现"把两种药水按一定比例混合",结果相当于"两个向量按一定比例相加"(系数非负,且和为1)。
我们可以将问题转化成,给定三个向量,是否存在一组和为1的非负系数,使得三个向量的线性组合为。
或者说,能否被表达为三个向量的凸组合。
根据凸包的定义,只需要检验是否在三瓶药水构成的凸包(三角形)内即可。
这里直接采用了C题的回转数法。
代码
//
// Created by vv123 on 2022/7/18.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 2e4 + 10;
struct point {
int x, y;
point operator-(const point &p) const { return {x - p.x, y - p.y}; }
int operator*(const point &p) const { return x * p.x + y * p.y; }
int operator^(const point &p) const { return x * p.y - p.x * y; }
int quad() const {
if (y > 0 || (y == 0 && x >= 0)) return 1;
return 2;
}//下平面定义为[-180,0),上平面定义为[0,180)
int toleft(const point &p) { //p在这个向量的逆时针方向,则叉积为正
int t = (*this) ^ p;
if (t > 0) return 1;
if (t == 0) return 0;
if (t < 0) return -1;
}
};
bool cmp(const point &a,const point &b) {
if (a.quad() != b.quad()) return a.quad() < b.quad();
else return (a ^ b) > 0;
}
struct line {
point p, v;
int toleft(const point &a) {
return v.toleft(a - p);
}
};
struct polygon {
vector<point> vec;
int nxt(int i) { return (i + 1) % vec.size(); }
pii winding(const point &p) {
int res = 0;
for (int i = 0; i < vec.size(); i++) {
point u = vec[i], v = vec[nxt(i)];
if (((p - u) ^ (p - v)) == 0 && (p - u) * (p - v) <= 0) return {1, 0};
if (u.y == v.y) continue;
line uv = {u, v - u};
if (u.y < v.y && uv.toleft(p) <= 0) continue;
if (u.y > v.y && uv.toleft(p) >= 0) continue;
if (u.y < p.y && v.y >= p.y) res++;
if (u.y >= p.y && v.y < p.y) res--;
}
return {0, res};
};
} poly;
void solve() {
int n, m;
//cin >> n;
n = 3;
for (int i = 1; i <= n; i++) {
int x, y;
cin >> x >> y;
poly.vec.push_back({x, y});
}
cin >> m;
while (m--) {
int x, y;
cin >> x >> y;
point p = {x, y};
pii res = poly.winding(p);
if (res.first == 1 || res.second != 0) puts("YES");
else puts("NO");
}
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
F-多重和式 反演
题意
给定, 求
解题思路
- 莫比乌斯函数:
- 反演结论:
- 莫比乌斯变换:
- 形式1
- 形式2
- 基础例题:(考虑枚举d)
考虑将lcm转化为gcd,有
注意到两个数的gcd等于d,当且仅当它们除以d后互质。
令,则原式
考虑如何求出.
令(n,m为已确定的常数)
那么
令,那么
根据形式2,
下面考虑如何快速求出。根据前面类似的手法,可以得到
从而
代入原式求解即可
使用了两层整除分块,时间复杂度为
这里第一次接触二维的整除分块。一般只需要将$r =\left \lfloor \frac{n}{\left \lfloor \frac{n}{l} \right \rfloor} \right \rfloor r =\min(\left \lfloor \frac{n}{\left \lfloor \frac{n}{l} \right \rfloor} \right \rfloor,\left \lfloor \frac{m}{\left \lfloor \frac{m}{l} \right \rfloor} \right \rfloor)r =\min(\left \lfloor \frac{n}{\left \lfloor \frac{n}{l} \right \rfloor} \right \rfloor,\left \lfloor \frac{m}{\left \lfloor \frac{m}{l} \right \rfloor} \right \rfloor, a)$
代码
//
// Created by vv123 on 2022/7/19.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, m, a, ans;
const int mod = 1e9 + 7;
const int N = 1e7 + 10;
int pow(int a, int b, int p) {
int res = 1;
for (; b; b >>= 1) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
}
return res;
}
int vis[N], prime[N], mu[N];
int s[N], sum[N];
void Shai(int n = N - 1) {
vis[1] = mu[1] = 1;
int cnt = 0;
for (int i = 2; i <= n; i++) {
if (!vis[i]) {
prime[++cnt] = i;
mu[i] = -1;
}
for (int j = 1; j <= cnt && i * prime[j] <= n; j++) {
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) {
mu[i * prime[j]] = 0; //p1的指数大于1,莫比乌斯函数为0
break;
} else {
mu[i * prime[j]] = -mu[i];
}
}
}
s[0] = sum[0] = 0;
for (int i = 1; i <= n; i++) {
s[i] = (s[i - 1] + i * i % mod * mu[i] % mod) % mod;
sum[i] = (sum[i - 1] + i) % mod;
}
}
inline int g(int n, int m) {
int res = 0;
for (int l = 1, r; l <= min(n, m); l = r + 1) {
r = min(n / (n / l), m / (m / l));
int t = (n / l) * (n / l + 1) % mod * (m / l) % mod * (m / l + 1) % mod * pow(4, mod - 2, mod) % mod;
res = (res + (s[r] - s[l - 1] + mod) % mod * t % mod) % mod;
}
return res;
}
void solve() {
Shai();
cin >> n >> m >> a;
int ans = 0;
for (int l = 1, r; l <= min(min(n, m), a); l = r + 1) {
r = min(a, min(n / (n / l), m / (m / l)));
int t = g(n / l, m / l);
//cout << l << " " << r << " " << (sum[r] - sum[l - 1] + mod) % mod * t % mod << "\n";
ans = (ans + (sum[r] - sum[l - 1] + mod) % mod * t % mod) % mod;
}
cout << ans << "\n";
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
G-数三角形 几何
题意
在二维平面上有 个点,请问以这些点为顶点可以形成多少个不同的锐角三角形。(保证坐标两两不同)
解题思路
我们将锐角定义为,非锐角定义为。那么任选三个点,只有两种情况:
- 有一个非锐角和两个锐角。包括直角三角形、钝角三角形和三点共线。
- 有三个锐角,为锐角三角形。
因此设锐角、非锐角的个数分别为,显然锐角三角形的个数为
又因为总共有个角,只需统计出非锐角的个数。考虑枚举每一个点作为顶点,将其他点进行极角排序。
这里将极角按照排序。考虑将第条边作为始边,指针指向逆时针方向的一个大于等于(第一个不满足(v[i] ^ v[l]) >= 0 && v[i] * v[l] > 0
)的位置,指针指向逆时针方的第一个大于(第一个满足(v[i] ^ v[l]) < 0 && v[i] * v[l] > 0
)的位置,则以第条边作为始边构成的非锐角个数为;逆时针枚举每一条边,指针是单调的,可以统计出非锐角个数。
时间复杂度的瓶颈来自次极角排序,为。
代码
//
// Created by vv123 on 2022/7/13.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e4 + 10;
struct point {
int x, y;
point operator-(const point &p) const { return {x - p.x, y - p.y}; }
int operator*(const point &p) const { return x * p.x + y * p.y; }
int operator^(const point &p) const { return x * p.y - p.x * y; }
int quad() const {
if (y > 0 || (y == 0 && x >= 0)) return 1;
return 2;
}//下平面定义为[-180,0),上平面定义为[0,180)
} p[N], v[N];
bool cmp(const point &a,const point &b) {
if (a.quad() != b.quad()) return a.quad() < b.quad();
else return (a ^ b) > 0;
}
int n, R, D;
void work(int id) {
point O = p[id];
int m = 0;
for (int i = 1; i <= n; i++) {
if (i == id) continue;
v[++m] = p[i] - O;
}
sort(v + 1, v + 1 + m, cmp);
//for (int i = 1; i <= m; i++) cout << v[i].x << " " << v[i].y << "\n";
int l = 1, r = 1;
for (int i = 1; i <= m; i++) {
while (l <= m && (v[i] ^ v[l]) >= 0 && v[i] * v[l] > 0) l++;
while (r <= m && ((v[i] ^ v[r]) >= 0 || v[i] * v[r] <= 0)) r++;
//cout << i << " " << l << " " << r << "\n";
D += r - l;
}
}
void solve() {
D = 0;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> p[i].x >> p[i].y;
}
for (int i = 1; i <= n; i++) {
work(i);
}
//cout << D << endl;
R = n * (n - 1) * (n - 2) / 2 - D;
cout << (R - D * 2) / 3 << "\n";
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
H-洛水神女 水题
题意
有只猫,每只猫在生日之后有的概率死亡,求他们的期望寿命之和 对取模.
解题思路
设一只猫每次生日后的死亡率为,则它的寿命期望为
$\sum \limits_{i=1}{\infty}i(1-p)p =-p\sum \limits_{i=1}{\infty}[(1-p)i]'=-p[\sum \limits_{i=1}{\infty}(1-p)i]'=-p(\frac{1}{p}-1)'=\frac{1}{p} $
因此只需求出
根据费马小定理,
代码
//
// Created by vv123 on 2022/7/12.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
typedef long long ll;
pair<signed, signed> nextPair(signed &x) {
signed a, b;
x ^= x << 13; x ^= x >> 17; x ^= x << 5; x %= 10000000; a = x;
x ^= x << 13; x ^= x >> 17; x ^= x << 5; x %= 10000000; b = x;
b = max(b, 1); a = max(a % b, 1);
//cout << a << " " << b << "\n";
return make_pair(a, b);
}
int pow(int a, int b, int p) {
int res = 1;
for (; b; b >>= 1) {
if (b & 1) res = res * a % p;
a = a * a % p;
}
return res;
}
void solve() {
int n; signed x;
cin >> n >> x;
ll ans = 0;
for (int i = 1; i <= n; i++) {
pair<signed, signed> t = nextPair(x);
ans = (ans + 1ll * t.second % mod * 1ll * pow(t.first, mod - 2, mod)) % mod;
}
cout << ans << "\n";
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
N-黑灰游戏 改 博弈
题意
有个石子堆,每堆有个石子。
奇数轮由Kate指定一堆石子,Emilico从中拿走至少一个石子。
偶数轮由Emilico指定一堆石子,Kate从中拿走至少一个石子。
拿走最后一个石子的一方获胜。问Emilico是否有必胜策略。
解题思路
我们将石子堆分为两类:
-
A类:只有一个石子
-
B类:多于一个石子
考虑最简单的情况:如果所有石子堆都只有一个石子(都是A类堆),显然当堆数为奇数时Emilico获胜。
考虑只有一个B类堆。
如果上一种情况的胜者指定了B类堆,上一种情况的败者一定会将它变成A类堆,改变胜负。
如果上一种情况的败者指定了B类堆,上一种情况的胜者一定会把它拿完,依然获胜。
不管哪种情况,都是指定了B类堆的一方失败。
因此,双方一定不会主动指定B类堆,不得不指定B类堆的一方必败。
容易发现:A类堆个数为偶数时,B类堆一定会被Kate指定,从而Emilico获胜。否则Kate获胜。
下面使用数学归纳法证明:存在K(K ≥ 1)个B类堆时,结论都是"A类堆个数为偶数数时Emilico获胜"
首先K=1的情况已被证明。
假设当B类堆个数为K时,结论是"A类堆个数为偶数时Emilico获胜",则当B类堆个数为K+1时:
考虑哪一方指定了第一个B类堆。
如果B类堆个数为K时的胜者指定了第一个B类堆,B类堆个数为K时的败者一定会将它变成A类堆,使胜负与B类堆个数为K的情况相反,得以取胜。
如果B类堆个数为K时的败者指定了第一个B类堆,B类堆个数为K时的胜者者一定会将它取完,使胜负与B类堆个数为K的情况相同,得以取胜。
因此不管哪种情况,都是不得不指定第一个B类堆的一方失败,由Kate还是Emilico被迫指定第一个B类堆,只与A类堆的数量奇偶性有关。结论仍为"A类堆个数为偶数时Emilico获胜"。因此结论对任意正整数K都成立。
综上所述,只有奇数个A类堆,或者存在B类堆且A类堆个数为为偶数时,Emilico获胜。
代码
//
// Created by vv123 on 2022/7/20.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define seed 131
using namespace std;
const int mod = 1e9 + 7;
const int N = 2e6 + 10;
const int inf = 0x3f3f3f3f;
void solve() {
int n, t, cntA = 0, cntB = 0;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> t;
if (t == 1) cntA++;
else cntB++;
}
if ((cntB == 0) ^ (cntA % 2 == 0)) puts("Win");
else puts("Lose");
}
signed main() {
int T = 1;
cin >> T;
while (T--) {
solve();
}
return 0;
}
Q-挑战新高考 反演 杜教筛
题意
从L至R中的R-L+1个整数中随机取2个不同的数。求这2个数互质的概率。
解题思路
我们在课上学过
同样地,我们考虑的倍数在中的出现次数,有
(ppt上的结论有误)
注意到互质的除外都可以互换,因此如果,上述和式的结果是符合要求的排列数+1,否则恰好为所有可能的排列个数。
因此此题答案为
用数论分块进行计算的过程中需要用到莫比乌斯函数的前缀和,可以使用杜教筛。
杜教筛被用来处理数论函数的前缀和问题。对于求解一个前缀和,杜教筛可以在低于线性时间的复杂度内求解。
对于数论函数 ,要求我们计算 .
我们想办法构造一个 关于 的递推式
对于任意一个数论函数 ,必满足
略证:
就是对所有 的做贡献,因此变换枚举顺序,枚举 (分别对应新的 )
那么可以得到递推式
那么假如我们可以快速对 求和,并用数论分块求解 就可以在较短时间内求得 .
由狄利克雷卷积,我们知道:
()
观察到 最多只有 种取值,我们就可以应用数论分块来计算每一项的值了。
直接计算的时间复杂度为 。考虑先线性筛预处理出前 项,剩余部分的时间复杂度为
对于较大的值,可以用 unordered_map
存下其对应的值,方便以后使用时直接使用之前计算的结果。
代码
//
// Created by vv123 on 2022/7/29.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, m, a, ans;
const int N = 1e7 + 10;
int min(int a, int b, int c) {
return min(min(a, b), c);
}
int pow(int a, int b, int p) {
int res = 1;
for (; b; b >>= 1) {
if (b & 1) res = res * a % p;
a = a * a % p;
}
return res;
}
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
int vis[N], prime[N], mu[N], sum[N];
void Shai(int n = N - 1) {
vis[1] = mu[1] = 1;
int cnt = 0;
for (int i = 2; i <= n; i++) {
if (!vis[i]) {
prime[++cnt] = i;
mu[i] = -1;
}
for (int j = 1; j <= cnt && i * prime[j] <= n; j++) {
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) {
mu[i * prime[j]] = 0;
break;
} else {
mu[i * prime[j]] = -mu[i];
}
}
}
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + mu[i];
}
}
unordered_map<int, int> sum_mu;
inline int get_mu(int x) {
if (x < N) return sum[x];
if (sum_mu[x]) return sum_mu[x];
int res = 1;
for (int l = 2, r; l <= x; l = r + 1) {
r = x / (x / l);
res -= (r - l + 1) * get_mu(x / l);
}
return sum_mu[x] = res;
}
int f(int L, int R) {
int res = 0;
for (int l = 1, r; l <= L; l = r + 1) {
r = min(L / (L / l), R / (R / l));
int t = ((R / l) - (L / l)) * ((R / l) - (L / l));
res += (get_mu(r) - get_mu(l - 1)) * t;
}
for (int l = L + 1, r; l <= R; l = r + 1) {
r = R / (R / l);
int t = (R / l) * (R / l);
res += (get_mu(r) - get_mu(l - 1)) * t;
}
return res;
}
int bf(int L, int R) {
int res = 0;
for (int i = L; i <= R; i++)
for (int j = L; j <= R; j++)
if (gcd(i, j) == 1) res++;
return res;
}
void solve() {
int L, R;
cin >> L >> R;
int a = f(L-1, R) - (L == 1), b = (R - L + 1) * (R - L), d = gcd(a, b);
cout << a / d << "/" << b / d << "\n";
}
signed main() {
Shai();
int T = 1;
cin >> T;
while (T--) {
solve();
}
return 0;
}
S- For D. E. Knuth 拓展欧拉定理
题意
求
解题思路
前置知识:拓展欧拉定理
由题意,
于是当且充分大时,有
注意到欧拉函数的多次嵌套很快会收敛到,因此可以递归计算。
我们设
则有
......
我们发现是一个很大的数字
这可以确保在时一定成立.
并且由于,因此拓展欧拉定理的第三条对也适用
因此采取这种方法计算,结果一定是正确的
代码
//
// Created by vv123 on 2022/7/10.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
int a, b, p;
inline 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 gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
int pow(int a, int b, int p) {
if (gcd(a, p) == 1) b %= phi(p);
if (gcd(a, p) != 1 && b >= phi(p)) b = b % phi(p) + phi(p);
int res = 1;
for (; b; b >>= 1) {
if (b & 1) res = res * a % p;
a = a * a % p;
}
return res;
}
int f(int a, int b, int p) {
if (p == 1) return 0;
else if (b > 5){
int t = pow(a, f(a, b - 1, phi(p)) + phi(p), p);
//printf("f(%d,%d,%d)=%d\n", a, b, p, t);
return t;
} else {
int res = 1;
for (int i = 1; i <= b; i++) {
res = pow(a, res, p);
}
return res;
}
}
void solve() {
cin >> a >> b >> p;
if (a == 1) {
if (p == 1) puts("0");
else cout << 1 << "\n";
return;
}
cout << f(a, b, p) << "\n";
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
W-奥日与风袭废墟 线性筛
题意
求的前项
解题思路
以上积性函数在质数的取值通常可以由定义得到。
而对于合数,线性筛保证了每次由最小的质因子筛掉。我们设合数,是它的最小质因子。则有以下两种情况
-
,则与互质,根据积性函数的性质,
-
,则也是的最小质因子。
设。其中是它的最小质因子。我们求一个辅助函数
那么
而是很好求的:
-
当,被唯一的最小质因子筛去,。
-
当,只需在的基础上添加一个的贡献即可。
代码
//
// Created by vv123 on 2022/7/8.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 500000 + 10;
int n;
int vis[N], prime[N], phi[N], mu[N], c[N], d[N], g[N], f[N], F[N], G[N], gg[N];
void Shai() {
vis[1] = phi[1] = mu[1] = d[1] = g[1] = f[1] = G[1] = gg[1] = F[1] = 1;
int cnt = 0;
for (int i = 2; i <= n; i++) {
if (!vis[i]) {
prime[++cnt] = i;
phi[i] = i - 1;
mu[i] = -1;
c[i] = 1; //质数的最小质因子指数为1
d[i] = c[i] + 1; //质数的约数个数为2。d[n] = PI(c_i + 1)
g[i] = i + 1;//g[i]为i的最小质因子0~k次方和,约数和f[i]为所有k次质因子g[i]的积
gg[i] = i;
f[i] = g[i];
G[i] = mu[1] * phi[1] * f[i] + mu[i] * phi[i] * f[1];
F[i] = G[i];
}
for (int j = 1; j <= cnt && i * prime[j] <= n; j++) {
//printf("i=%d prime[j]=%d\n", i, prime[j]);
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) {
//printf("%d mod %d == 0\n", i, prime[j]);
phi[i * prime[j]] = prime[j] * phi[i]; //n'包含了n的所有质因子,与n的欧拉函数只差p1
mu[i * prime[j]] = 0; //p1的指数大于1,莫比乌斯函数为0
c[i * prime[j]] = c[i] + 1; //注意到i和i * prime[j]的最小质因子都是prime[j],后面都很显然了
d[i * prime[j]] = d[i] / (c[i] + 1) * (c[i * prime[j]] + 1);
g[i * prime[j]] = g[i] * prime[j] + 1;
gg[i * prime[j]] = gg[i] * prime[j];
f[i * prime[j]] = f[i] / g[i] * g[i * prime[j]];
G[i * prime[j]] = G[i] + gg[i]; //
F[i * prime[j]] = F[i] / G[i] * G[i * prime[j]];
break;
} else {
//printf("%d mod %d == %d\n", i, prime[j], i % prime[j]);
phi[i * prime[j]] = phi[prime[j]] * phi[i]; //最小质因子不整除其他部分,n'和p1互质
mu[i * prime[j]] = mu[prime[j]] * mu[i];
c[i * prime[j]] = 1;
d[i * prime[j]] = d[prime[j]] * d[i];
g[i * prime[j]] = 1 + prime[j];
gg[i * prime[j]] = prime[j];
f[i * prime[j]] = f[prime[j]] * f[i];
G[i * prime[j]] = G[prime[j]];
F[i * prime[j]] = F[prime[j]] * F[i];
}
}
}
}
void solve() {
cin >> n;
Shai();
for (int i = 1; i <= n; i++) printf("%d ", phi[i]); puts("");
for (int i = 1; i <= n; i++) printf("%d ", mu[i]); puts("");
for (int i = 1; i <= n; i++) printf("%d ", d[i]); puts("");
for (int i = 1; i <= n; i++) printf("%d ", f[i]); puts("");
//for (int i = 1; i <= n; i++) printf("%d ", gg[i]); puts("");
//for (int i = 1; i <= n; i++) printf("%d ", G[i]); puts("");
for (int i = 1; i <= n; i++) printf("%d ", F[i]); puts("");
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
X-多项式乘法 FFT
题意
给出两个多项式的系数,求两个多项式乘积的系数
解题思路
FFT是这样一种东西:通过由分治实现的DFT,我们可以将一个多项式从系数表示法转化成点值表示法。已知两个多项式的点值表示,则它们的乘积的点值表示就是点值的乘积。我们得到乘积的点值表示后,可以经过 IDFT(傅里叶反变换)的作用转换成系数形式 。
代码
//
// Created by vv123 on 2022/7/11.
//
#include <bits/stdc++.h>
#define int long long
#define pi M_PI
using namespace std;
typedef complex<double> cp;
const cp I(0, 1);
const int N = 5e6 + 10;
cp t[N], f[N], g[N], h[N];
int a[N], b[N];
void fft(cp *f, int n, int rev) {
if (n == 1) return;
for (int i = 0; i < n; i++) t[i] = f[i];
for (int i = 0; i < n; i++) {
if (i % 2) f[n / 2 + i / 2] = t[i];
else f[i / 2] = t[i];
}
cp *g = f, *h = f + n / 2;
fft(g, n / 2, rev); fft(h, n / 2, rev);
cp cur(1, 0), step = exp(I * (2 * pi / n * rev));
for (int k = 0; k < n / 2; k++) {
t[k] = g[k] + cur * h[k];
t[k + n / 2] = g[k] - cur * h[k];
cur *= step;
}
for (int i = 0; i < n; i++) f[i] = t[i];
}
void solve() {
int n, m;
cin >> n >> m;
for (int i = 0; i <= n; i++) {
int x;
cin >> x;
f[i].real(x);
}
for (int i = 0; i <= m; i++) {
int x;
cin >> x;
g[i].real(x);
}
int k = 1;
while (k <= n + m) k <<= 1;
fft(f, k, 1); fft(g, k, 1);
for (int i = 0; i <= k; i++)
h[i] = f[i] * g[i];
fft(h, k, -1);
for (int i = 0; i <= n + m; i++)
cout << (int) ((h[i].real() / k) + 0.5) << " ";
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律