2025.5.10-5.20 学习随记(数学)
1.筛质数
感觉不是很难,稍微想想应该可以。
2.分解质因数
分解质因数的运用。
3.快速幂
4.约数个数
5.欧拉函数
公式是 phi(n)=n(1-1/p1)(1-1/p2)...(1-1/pk),
p1~pk 为 n 的质因数。
然后可以通过筛法求 1~n 每个数的欧拉函数。
6.同余和 CRT
同余方程一般形式为 ax+by=1,可通过 exgcd 求得一组通解。
当然也有形似 ax+by=c 的方程,此时若是 (a,b) 不整除 c 则方程无解。
然后是 CRT。
结论是 x=aiM/miinv(M/mi),其中 M 表示所有 mi 的乘积,inv 为逆元,所以这个东西又要 exgcd。
7.矩阵乘法
优化 DP 的一种策略。
一般系数放第二个矩阵,变量放第一个矩阵。
8.组合计数
主要就是卡特兰数和 Lucas 定理。
卡特兰数的题目一般是要看题目中能不能转化为前缀中...>... 的形式。
然后 Lucas 定理就是 Cn,m%p=Cn%p,m%p*Cn/p,m/p。
P3200 [HNOI2009] 有趣的数列(卡特兰数)
比较巧妙吧,但是感觉不是很难想到。
就是当你对于一个数想要放在偶数位时,必须保证他前面放奇数位的数个数必须大于放偶数位的数个数。
然后就转化为满足前缀中 ...>... 的形式了,直接卡特兰数即可。
不过有一个比较需要注意就是要用分解质因数 + qp 做,不然会 T 飞。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2000010;
int n, p;
bool st[N];
int primes[N], cnt;
void init()
{
st[1] = true;
for (int i = 2; i < N; i ++ )
{
if (!st[i]) primes[ ++ cnt] =i;
for (int j = 1; primes[j] * i < N; j ++ )
{
st[i * primes[j]] = true;
if (i % primes[j] == 0) break;
}
}
}
int get(int n, int x)
{
int s = 0;
while (n)
{
s += n / x;
n /= x;
}
return s;
}
int quick_power(int a, int b)
{
int res = 1;
while (b)
{
if (b & 1) (res *= a) %= p;
(a *= a) %= p;
b >>= 1;
}
return res;
}
int C(int n, int m)
{
if (n < m) return 0;
int res = 1;
for (int i = 1; i <= cnt; i ++ )
{
int num = get(n, primes[i]) - get(m, primes[i]) - get(n - m, primes[i]);
(res *= quick_power(primes[i], num)) %= p;
}
return res;
}
signed main()
{
cin >> n >> p;
init();
cout << ((C(n * 2, n) - C(n * 2, n - 1)) % p + p) % p << '\n';
return 0;
}
9.高斯消元
先找系数最大的一行,避免精度误差过大。
然后把这一行系数变为 1,再消去剩下的方程组中本项系数。
球形空间产生器(Gauss消元)
#include <bits/stdc++.h>
using namespace std;
const int N = 20;
const double eps = 1e-9;
int n;
double a[N][N], b[N][N];
void gauss()
{
for (int r = 1, c = 1; c <= n; c ++ )
{
int p = r;
for (int i = r + 1; i <= n; i ++ )
if (fabs(b[i][c]) > fabs(b[p][c])) p = i;
if (fabs(b[p][c]) < eps) continue;
for (int i = c; i <= n + 1; i ++ ) swap(b[r][i], b[p][i]);
for (int i = n + 1; i >= c; i -- ) b[r][i] /= b[r][c];
for (int i = r + 1; i <= n; i ++ )
if (fabs(b[i][c]) > eps)
for (int j = n + 1; j >= c; j -- )
b[i][j] -= b[r][j] * b[i][c];
r ++ ;
}
// if (r < n)
// {
// for (int i = r; i <= n; i ++ )
// if (fabs(b[i][n + 1]) > eps)
// return 2;
// return 1;
// }
for (int i = n; i > 1; i -- )
for (int j = i - 1; j; j -- )
{
b[j][n + 1] -= b[i][n + 1] * b[j][i];
b[j][i] = 0;
}
}
signed main()
{
cin >> n;
for (int i = 1; i <= n + 1; i ++ )
for (int j = 1; j <= n; j ++ )
cin >> a[i][j];
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
b[i][j] = 2 * (a[i + 1][j] - a[1][j]),
b[i][n + 1] += a[i + 1][j] * a[i + 1][j] - a[1][j] * a[1][j];
gauss();
for (int i = 1; i <= n; i ++ ) printf("%.3lf ", b[i][n + 1]);
cout << '\n';
return 0;
}
开关问题
#include <bits/stdc++.h>
using namespace std;
const int N = 35;
int n;
int a[N][N];
int gauss()
{
int r, c;
for (r = 1, c = 1; c <= n; c ++ )
{
int t = r;
for (int i = r + 1; i <= n; i ++ )
if (a[i][c]) t = i;
if (!a[t][c]) continue;
for (int i = c; i <= n + 1; i ++ ) swap(a[t][i], a[r][i]);
for (int i = r + 1; i <= n; i ++ )
for (int j = n + 1; j >= c; j -- )
a[i][j] ^= a[i][c] & a[r][j];
r ++ ;
}
if (r <= n)
{
int res = 1;
for (int i = r; i <= n; i ++ )
{
if (a[i][n + 1]) return -1;
res *= 2;
}
return res;
}
return 1;
}
signed main()
{
int T;
cin >> T;
while (T -- )
{
memset(a, 0, sizeof a);
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> a[i][n + 1];
for (int i = 1; i <= n; i ++ )
{
int x;
cin >> x;
a[i][n + 1] ^= x;
a[i][i] = 1;
}
int x, y;
while (cin >> x >> y, x || y) a[y][x] = 1;
int t = gauss();
if (t == -1) puts("Oh,it's impossible~!!");
else cout << t << '\n';
}
return 0;
}
10.容斥原理
值得一提的是 Mobius 函数。
莫比乌斯函数就是对于 n,若分解质因数后,每个质因数有多个,则函数值为 0;
反之若质因数个数为奇数,则为 1;若质因数个数为偶数,则为 -1。刚好对应了容斥的奇减偶加。
可以比较巧妙的结合入题目。
CF451E Devu and Flowers
容斥的时候,考虑一个限制不满足之后,就相当于变为一个子问题:在 n 个盒子中继续取 m-ai-1。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 110, mod = 1e9 + 7;
int n, m;
int a[N], inv[N];
int quick_power(int a, int b)
{
int res = 1;
while (b)
{
if (b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
int C(int n, int m)
{
if (n < m) return 0;
int res = 1;
for (int i = n - m + 1; i <= n; i ++ ) (res *= i % mod) %= mod;
for (int i = 1; i <= m; i ++ ) (res *= inv[i]) %= mod;
return res;
}
signed main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
for (int i = 1; i <= n; i ++ ) inv[i] = quick_power(i, mod - 2);
int res = 0;
for (int i = 0; i < (1 << n); i ++ )
{
int sum = m, cnt = 0;
for (int j = 1; j <= n; j ++ )
{
if (!(i >> (j - 1) & 1)) continue;
sum -= a[j] + 1;
cnt ++ ;
}
if (cnt & 1) (res = (res - C(sum - 1 + n, n - 1)) % mod + mod) %= mod;
else (res += C(sum - 1 + n, n - 1)) %= mod;
}
cout << res << '\n';
return 0;
}
P3455 [POI 2007] ZAP-Queries(破译密码)
莫比乌斯函数和容斥的题目关联的可能比较多吧。
莫比乌斯函数就是对于 n,若分解质因数后,每个质因数有多个,则函数值为 0;
反之若质因数个数为奇数,则为 1;若质因数个数为偶数,则为 -1。刚好对应了容斥的奇减偶加。
然后莫比乌斯函数是一个积性函数,可以在线性筛质数的时候求。
对于这题,先转化为求 gcd(x/d,y/d)=1 的 (x,y) 对数。
然后就是总对数减去公约数一个的对数,加上公约数两个的对数……
于是前面的系数为莫比乌斯函数,考虑将所有数分段,因为最终 floor(a/i) 取值只有 2sqrt(a) 种。
这个很好证,前 sqrt(a) 个数最多有 sqrt(a) 个,后 sqrt(a) 个数取值最多 sqrt(a) 个。
主要瓶颈在于求每段的右端点和证明。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 50010;
int n;
int primes[N], cnt;
int mobius[N];
bool st[N];
void init(int n)
{
mobius[1] = 1;
for (int i = 2; i <= n; i ++ )
{
if (!st[i])
{
primes[ ++ cnt] = i;
mobius[i] = -1;
}
for (int j = 1; primes[j] * i <= n; j ++ )
{
st[i * primes[j]] = 1;
if (i % primes[j] == 0)
{
mobius[i * primes[j]] = 0;
break;
}
mobius[i * primes[j]] = -mobius[i];
}
}
for (int i = 1; i <= n; i ++ ) mobius[i] += mobius[i - 1];
}
signed main()
{
init(N - 1);
cin >> n;
while (n -- )
{
int a, b, d;
scanf("%lld%lld%lld", &a, &b, &d);
int res = 0;
a /= d, b /= d;
for (int l = 1, r; l <= min(a, b); l = r + 1)
{
r = min(min(a, b), min(a / (a / l), b / (b / l)));
res += (mobius[r] - mobius[l - 1]) * (a / l) * (b / l);
}
cout << res << '\n';
}
return 0;
}
11.概率与数学期望
\(E(j)=E(i)*P(i)+W(i->j)\)
12.博弈论
比较著名的看了下,比较杂、多,不写了。
P2252 [SHOI2002] 取石子游戏/【模板】威佐夫博弈
#include <bits/stdc++.h>
#define int long long
using namespace std;
signed main()
{
int a, b;
cin >> a >> b;
if (a > b) swap(a, b);
if ((int)((sqrt(5) + 1.0) / 2.0 * (b - a)) == a) cout << "0\n";
else cout << "1\n";
return 0;
}
P2599 [ZJOI2009] 取石子游戏
博弈论的题是不是都是神仙题……
设 l[i][j] 表示区间 [i,j] 左边放数量为 l[i][j] 的石子堆可以使得先手必败。
相对的,设 r[i][j] 表示区间 [i,j] 右边放数量为 r[i][j] 的石子堆可以使得先手必败。
不难发现这有性质:l,r 一定是唯一确定的。
根据 SG 函数来看,若存在两个取值 X1<X2为必败态,则对于 X2 来说可以转化到 X1 的情况,那么 X2 应是必胜态。
考虑递推,大力分讨。
只讨论左边的更新即可,右边对称。
设 L 表示 l[i][j-1],R 表示 r[i][j-1],X 表示 a[j]。
1.X=R
则区间 [i,j] 已为必败态,在左边不需要放,l[i][j] 取 0。
2.X<R,X<L
无论怎么取,X' 都一定小于 L,R。
因为必败态的取值唯一,所以我们只要保证当先手将左右一堆石子取空时,另一堆仍有即可,那么留给后手的就是必胜态。
故直接填 X 即可。
3.R<L,R<X<=L
设 Y 表示 l[i][j]。
考虑先手取的是左边还是右边。
如果是左边的话,当取完数量仍在 [R,L) 间,后手就保持原来的相对关系;
当取完数量小于 R,那么后手就立即把另一堆取成与左边相同,情况就变成 2 中的必败态了。
如果是右边的话,显然不能取到 R,若取完大于 R,则保持相对关系不变;若取完小于 R,则取成与 R 相等,同 2。
最终结论是填 X-1 即可。
4.L<R,L<X<=R
同 3 类似,结论是填 X+1。
5.X>L,X>R
同 2 类似,结论是填 X。
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, a[N];
int l[N][N], r[N][N];
signed main()
{
int T;
cin >> T;
while (T -- )
{
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
for (int len = 1; len <= n; len ++ )
{
for (int i = 1; i + len - 1 <= n; i ++ )
{
int j = i + len - 1, L, R, X;
L = l[i][j - 1], R = r[i][j - 1], X = a[j];
if (X == R) l[i][j] = 0;
else if (X < min(L, R) || X > max(L, R)) l[i][j] = X;
else if (L < R) l[i][j] = X + 1;
else l[i][j] = X - 1;
L = l[i + 1][j], R = r[i + 1][j], X = a[i];
if (X == L) r[i][j] = 0;
else if (X < min(L, R) || X > max(L, R)) r[i][j] = X;
else if (L > R) r[i][j] = X + 1;
else r[i][j] = X - 1;
}
}
if (n == 1) cout << "1\n";
else cout << (l[2][n] != a[1]) << '\n';
}
return 0;
}
本文来自博客园,作者:爱朝比奈まふゆ的MafuyuQWQ。 转载请注明原文链接:https://www.cnblogs.com/MafuyuQWQ/p/18887844

浙公网安备 33010602011771号