分解质因数
对于某一合数X,有且只有一个大于根号X的质因数。
反证:假如有两个大于根号X的质因数,相乘大于X矛盾。
所以i 只需要 枚举到根号 X,然后X不断除以 i 可以把所有i的倍数全部筛掉,那么最后如果剩下一个大于1 的X ,则X一定是大于根号X的那个质因数。
#include <iostream>
#include <algorithm>
using namespace std;
void divide(int x)
{
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
{
int s = 0;
while (x % i == 0) x /= i, s ++ ;
cout << i << ' ' << s << endl;
}
if (x > 1) cout << x << ' ' << 1 << endl;
cout << endl;
}
int main()
{
int n;
cin >> n;
while (n -- )
{
int x;
cin >> x;
divide(x);
}
return 0;
}
试除法求质数
常用板子,不多说。
#include <iostream>
#include <algorithm>
using namespace std;
bool is_prime(int x)
{
if (x < 2) return false;
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
return false;
return true;
}
int main()
{
int n;
cin >> n;
while (n -- )
{
int x;
cin >> x;
if (is_prime(x)) puts("Yes");
else puts("No");
}
return 0;
}
埃氏筛
从小到大枚举所有的数,所有的倍数都会被删除,那么没有被标记已删除的数就是素数。
#include <iostream>
#include <algorithm>
using namespace std;
const int N= 1000010;
int primes[N], cnt;
bool st[N];
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (st[i]) continue;
primes[cnt ++ ] = i;
for (int j = i + i; j <= n; j += i)
st[j] = true;
}
}
int main()
{
int n;
cin >> n;
get_primes(n);
cout << cnt << endl;
return 0;
}
线性筛
对于任意合数x,假设pj是x的最小质因子,当 i 枚举到 x/pj 的时候,这个合数就会被筛掉,枚举之后的 i 没有意义。
例如:如果primes[j] > n / i ,那么筛的时候一乘就大于n了,没有意义。
#include <iostream>
#include <algorithm>
using namespace std;
const int N= 1000010;
int primes[N], cnt;
bool st[N];
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) primes[cnt ++ ] = i;
for (int j = 0; primes[j] <= n / i; j ++ )//
{
st[primes[j] * i] = true;//如果下面i % primes[j] != 0,因为primes[j]
//从小到大枚举,所以primes[j]< i 的最小质因子,所以primes[j]是primes[j]* i 的最小值因子
if (i % primes[j] == 0) break;
//如果i % primes[j] == 0,因为primes[j]是从小到大枚举的
// 那么primes[j]一定是i的最小质因子,也是primes[j]* i 的最小值因子
}
}
}
int main()
{
int n;
cin >> n;
get_primes(n);
cout << cnt << endl;
return 0;
}
试除法求约数
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
vector<int> get_divisors(int x)
{
vector<int> res;
for (int i = 1; i <= x / i; i ++ )
if (x % i == 0)
{
res.push_back(i);
if (i != x / i) res.push_back(x / i);//防止x=i*i;
}
sort(res.begin(), res.end());
return res;
}
int main()
{
int n;
cin >> n;
while (n -- )
{
int x;
cin >> x;
auto res = get_divisors(x);
for (auto x : res) cout << x << ' ';
cout << endl;
}
return 0;
}
约数个数
对于任意的一个整数
N
N
N,由算数基本定理得
N
=
p
a
1
⋅
p
a
2
⋅
.
.
.
.
.
.
p
a
k
N=p^{a_1}·p^{a_2}·......p^{a_k}
N=pa1⋅pa2⋅......pak,对于
N
N
N的每一个约数,对应着质因子次方的选法。就比如
2
⋅
6
⋅
8
=
2
5
⋅
3
1
2·6·8=2^5·3^1
2⋅6⋅8=25⋅31,对于
2
2
2来说可以选
0
—
—
5
0——5
0——5次,对于
3
3
3来说可以选
0
—
—
1
0——1
0——1次 , 所以约数个数为
(
a
1
+
1
)
⋅
(
a
2
+
1
)
.
.
.
.
.
.
(
a
k
+
1
)
。
(a_1+1)·(a2+1)......(a_k+1)。
(a1+1)⋅(a2+1)......(ak+1)。
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 110, mod = 1e9 + 7;
int main()
{
int n;
cin >> n;
unordered_map<int, int> primes;
//primes存的是每一个质因数的指数
while (n -- )
{
int x;
cin >> x;
for (int i = 2; i <= x / i; i ++ )
while (x % i == 0)
{
x /= i;
primes[i] ++ ;
}
if (x > 1) primes[x] ++ ;
}
//求质因数的指数
LL res = 1;
for (auto p : primes) res = res * (p.second + 1) % mod;//公式
cout << res << endl;
return 0;
}
这个地方要求熟练掌握使用auto遍历一个map:
p可以找到map中被赋值的数的下标。
约数之和
根据约数个数,很显然这道题就是把约数表达出来,依然用
2
⋅
6
⋅
8
=
2
5
⋅
3
1
2·6·8=2^5·3^1
2⋅6⋅8=25⋅31,它的每一个约数对应某一种取法,所以
(
2
0
+
2
1
+
2
2
+
2
3
+
2
4
+
2
5
)
⋅
(
3
0
+
3
1
)
(2^0+2^1+2^2+2^3+2^4+2^5)·(3^0+3^1)
(20+21+22+23+24+25)⋅(30+31) 就是把每一种约数表达出来并求和。
就相当于:
我们设对于任意的质数
p
p
p ,它的最高次为
a
a
a 次,那么我们可以让
t
=
1
t=1
t=1 ,循环
a
a
a 次,每次让
t
=
p
t
+
1
t=pt+1
t=pt+1,这样循环完a次之后就可以得到图片中以
p
i
p_i
pi为下标的某个括号里的和了。最后把每个质数的那个括号和累乘起来即可。
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 110, mod = 1e9 + 7;
int main()
{
int n;
cin >> n;
unordered_map<int, int> primes;
while (n -- )
{
int x;
cin >> x;
for (int i = 2; i <= x / i; i ++ )
while (x % i == 0)
{
x /= i;
primes[i] ++ ;
}
if (x > 1) primes[x] ++ ;
}
LL res = 1;
for (auto p : primes)
{
LL a = p.first, b = p.second;
LL t = 1;
while (b -- ) t = (t * a + 1) % mod;
res = res * t % mod;
}
cout << res << endl;
return 0;
}
欧几里得(辗转相除)
1.利用algorithm库函数
2.手写
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
欧拉函数(分解质因数+公式)
互质是公约数只有1的两个整数,叫做互质整数。——
g
c
d
(
a
,
b
)
=
1
说
a
,
b
互
质
gcd(a,b)=1说a,b互质
gcd(a,b)=1说a,b互质
如果N可以分解质因数:
那么对于N的欧拉函数为:
#include <iostream>
using namespace std;
int phi(int x)
{
int res = x;//相当与先乘上公式里的N
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
{ //res=res*(1 - 1/i)
//不能让1/i出来个小数 所以用下边式子等价
res = res / i * (i - 1);//公式
while (x % i == 0) x /= i;
}
if (x > 1) res = res / x * (x - 1);
return res;
}
int main()
{
int n;
cin >> n;
while (n -- )
{
int x;
cin >> x;
cout << phi(x) << endl;
}
return 0;
}
筛法求欧拉函数
给定一个正整数 n,求 1∼n 中每个数的欧拉函数之和。
输入格式
共一行,包含一个整数 n。
输出格式
共一行,包含一个整数,表示 1∼n 中每个数的欧拉函数之和。
数据范围
1≤n≤106
输入样例:
6
输出样例:
12
一个质数p,与其互质的数一定为p-1个,所以质数p的欧拉函数为p-1。
如果对pj是i的质因子和不是i的质因子讨论并套用公式得:
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1000010;
int primes[N], cnt;
int euler[N];
bool st[N];
void get_eulers(int n)
{
euler[1] = 1;
for (int i = 2; i <= n; i ++ )
{
if (!st[i])
{
primes[cnt ++ ] = i;
euler[i] = i - 1;
}
for (int j = 0; primes[j] <= n / i; j ++ )
{
int t = primes[j] * i;
st[t] = true;
if (i % primes[j] == 0)
{
euler[t] = euler[i] * primes[j];
break;
}
euler[t] = euler[i] * (primes[j] - 1);
}
}
}
int main()
{
int n;
cin >> n;
get_eulers(n);
LL res = 0;
for (int i = 1; i <= n; i ++ ) res += euler[i];
cout << res << endl;
return 0;
}
快速幂
快速幂就是把a的指数K分解成多个2的x次方相加(2进制)。
通过这步处理可以把快速幂的复杂度降为log (n).
举个例子:
具体见注释:
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
//预处理a的2的x次方,相当于每次给a*a,就可以实现指数递增
LL qmi(LL a, int k, int p)
{ //第一次进来存的是a的2的0次方,就是a自己
LL res = 1;
while (k)
{
if (k & 1) res = res * a % p;//如果当前k的二进制个位是1的话,乘等a;
a = a * a % p;//不管k的个位是1是0 a都平方一下。
k >>= 1;//k的二进制进一位,删掉末位。
}
return res;//返回答案
}
int main()
{
int n;
scanf("%d", &n);
while (n -- )
{ LL a;
int k, p;
scanf("%lld%d%d", &a, &k, &p);
printf("%lld\n", qmi(a, k, p));
}
return 0;
}
快速幂求逆元(mod为质数)
给定 n 组 ai,pi,其中 pi 是质数,求 ai 模 pi 的乘法逆元,若逆元不存在则输出 impossible。
注意:请返回在 0∼p−1 之间的逆元。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一个数组 ai,pi,数据保证 pi 是质数。
输出格式
输出共 n 行,每组数据输出一个结果,每个结果占一行。
若 ai 模 pi 的乘法逆元存在,则输出一个整数,表示逆元,否则输出 impossible。
数据范围
1≤n≤105,
1≤ai,pi≤2∗109
输入样例:
3
4 3
8 5
6 3
输出样例:
1
2
impossible
化简题目的表达式:
找逆元:
使用快速幂:
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
//预处理a的2的x次方,相当于每次给a*a,就可以实现指数递增
LL qmi(int a, int k, int p)
{ //第一次进来存的是a的2的0次方,就是a自己
LL res = 1;
while (k)
{
if (k & 1) res = res * a % p;//如果当前k的二进制个位是1的话,乘等a;
a = a * (LL)a % p;//不管k的个位是1是0 a都平方一下。
k >>= 1;//k的二进制进一位,删掉末尾。
}
return res;//返回答案
}
int main()
{
int n;
scanf("%d", &n);
while (n -- )
{
int a, k, p;
scanf("%d%d", &a, &p);
if(a%p!=0)
printf("%lld\n", qmi(a, p-2, p));//求逆元
else puts("impossible");
}
return 0;
}
博弈论
Nim游戏
给定 n 堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
输入格式
第一行包含整数 n。
第二行包含 n 个数字,其中第 i 个数字表示第 i 堆石子的数量。
输出格式
如果先手方必胜,则输出 Yes。
否则,输出 No。
数据范围
1≤n≤105,
1≤每堆石子数≤109
输入样例:
2
2 3
输出样例:
Yes
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int main()
{
int n;
scanf("%d", &n);
int res = 0;
while (n -- )
{
int x;
scanf("%d", &x);
res ^= x;
}
if (res) puts("Yes");
else puts("No");
return 0;
}
提前移除x堆
dp[1][1][0] = 1;
dp[1][0][a[1]] = 1;
for (int i = 2; i <= n; i++)
{
for (int j = 0; j <= i; j++)
{
for (int k = 0; k < 异或最值; k++)
{
dp[i][j][k] = (dp[i-1][j-1][k] + dp[i-1][j][k^a[i]]) % mod;
}
}
}
台阶-Nim游戏
现在,有一个 n 级台阶的楼梯,每级台阶上都有若干个石子,其中第 i 级台阶上有 ai 个石子(i≥1)。
两位玩家轮流操作,每次操作可以从任意一级台阶上拿若干个石子放到下一级台阶中(不能不拿)。
已经拿到地面上的石子不能再拿,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
输入格式
第一行包含整数 n。
第二行包含 n 个整数,其中第 i 个整数表示第 i 级台阶上的石子数 ai。
输出格式
如果先手方必胜,则输出 Yes。
否则,输出 No。
数据范围
1≤n≤105,
1≤ai≤109
输入样例:
3
2 1 3
输出样例:
Yes
所以奇数阶不为0的先手必胜。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int main()
{
int n;
scanf("%d", &n);
int res = 0;
for (int i = 1; i <= n; i ++ )
{
int x;
scanf("%d", &x);
if (i % 2) res ^= x;
}
if (res) puts("Yes");
else puts("No");
return 0;
}
SG函数:求集合中不包含的最小自然数。
比如{1,3,2}这个集合的SG值为0;
集合-Nim游戏
给定 n 堆石子以及一个由 k 个不同正整数构成的数字集合 S。
现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合 S,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
输入格式
第一行包含整数 k,表示数字集合 S 中数字的个数。
第二行包含 k 个整数,其中第 i 个整数表示数字集合 S 中的第 i 个数 si。
第三行包含整数 n。
第四行包含 n 个整数,其中第 i 个整数表示第 i 堆石子的数量 hi。
输出格式
如果先手方必胜,则输出 Yes。
否则,输出 No。
数据范围
1≤n,k≤100,
1≤si,hi≤10000
输入样例:
2
2 5
3
2 4 7
输出样例:
Yes
对于某一个点的SG函数,可以用一种“局面”来描述。
对于某一个点,如果它是终点,即无法操作,如果它不是终点,一定可以把局面变成必败态甩给对手。
同之前的NIm游戏证明。
最终把每一个点的SG异或起来,得0必败。
注释代码:
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_set>
using namespace std;
const int N = 110, M = 10010;
int n, m;
int s[N], f[M];
int sg(int x)
{
if (f[x] != -1) return f[x];//记忆化搜索,确定SG的点不再搜了。
unordered_set<int> S;//存每一个点的SG所形成的局面
for (int i = 0; i < m; i ++ )
{
int sum = s[i];//遍历对某堆石子可以拿走的石子块数。
if (x >= sum) S.insert(sg(x - sum));//能够形成哪些局面?递归下去
}
for (int i = 0; ; i ++ )
if (!S.count(i))//从小到大枚举当前点形成的局面里没有的最小自然数
return f[x] = i;//给这个点的SG赋值
}
int main()
{
cin >> m;
for (int i = 0; i < m; i ++ ) cin >> s[i];
cin >> n;//s[i]是对某一堆石子,可以拿走其中的几块?
memset(f, -1, sizeof f);
int res = 0;
for (int i = 0; i < n; i ++ )
{
int x;
cin >> x;
res ^= sg(x);
}
if (res) puts("Yes");//非0必胜
else puts("No");
return 0;
}
拆分-Nim游戏
给定 n 堆石子,两位玩家轮流操作,每次操作可以取走其中的一堆石子,然后放入两堆规模更小的石子(新堆规模可以为 0,且两个新堆的石子总数可以大于取走的那堆石子数),最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
输入格式
第一行包含整数 n。
第二行包含 n 个整数,其中第 i 个整数表示第 i 堆石子的数量 ai。
输出格式
如果先手方必胜,则输出 Yes。
否则,输出 No。
数据范围
1≤n,ai≤100
输入样例:
2
2 3
输出样例:
Yes
拆分还是利用了SG函数的性质,对于某一个局面可以由两个局面异或求得,所以如果一堆石子被两堆石子替代,实际上就是求两堆石子异或的一个过程,其他的地方和集合_nim差不多。
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_set>
using namespace std;
const int N = 110;
int n;
int f[N];
int sg(int x)
{
if (f[x] != -1) return f[x];
unordered_set<int> S;
for (int i = 0; i < x; i ++ )
for (int j = 0; j <= i; j ++ )
S.insert(sg(i) ^ sg(j));
for (int i = 0;; i ++ )
if (!S.count(i))
return f[x] = i;
}
int main()
{
cin >> n;
memset(f, -1, sizeof f);
int res = 0;
while (n -- )
{
int x;
cin >> x;
res ^= sg(x);
}
if (res) puts("Yes");
else puts("No");
return 0;
}
高斯消元解线性方程组
输入一个包含 n 个方程 n 个未知数的线性方程组。
方程组中的系数为实数。
求解这个方程组。
下图为一个包含 m 个方程 n 个未知数的线性方程组示例:
输入格式
第一行包含整数 n。
接下来 n 行,每行包含 n+1 个实数,表示一个方程的 n 个系数以及等号右侧的常数。
输出格式
如果给定线性方程组存在唯一解,则输出共 n 行,其中第 i 行输出第 i 个未知数的解,结果保留两位小数。
如果给定线性方程组存在无数解,则输出 Infinite group solutions。
如果给定线性方程组无解,则输出 No solution。
数据范围
1≤n≤100,
所有输入系数以及常数均保留两位小数,绝对值均不超过 100。
输入样例:
3
1.00 2.00 -1.00 -6.00
2.00 1.00 -3.00 -9.00
-1.00 -1.00 2.00 7.00
输出样例:
1.00
-2.00
3.00
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 110;
const double eps = 1e-6;
int n;
double a[N][N];
int gauss()
{
int c, r;
for (c = 0, r = 0; c < n; c ++ )
{
int t = r;
for (int i = r; i < n; i ++ )
if (fabs(a[i][c]) > fabs(a[t][c]))
t = i;
if (fabs(a[t][c]) < eps) continue;
for (int i = c; i < n + 1; i ++ ) swap(a[t][i], a[r][i]);
for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c];
for (int i = r + 1; i < n; i ++ )
if (fabs(a[i][c]) > eps)
for (int j = n; j >= c; j -- )
a[i][j] -= a[r][j] * a[i][c];
r ++ ;
}
if (r < n)
{
for (int i = r; i < n; i ++ )
if (fabs(a[i][n]) > eps)
return 2;
return 1;
}
for (int i = n - 1; i >= 0; i -- )
for (int j = i + 1; j < n; j ++ )
a[i][n] -= a[j][n] * a[i][j];
return 0;
}
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n + 1; j ++ )
cin >> a[i][j];
int t = gauss();
if (t == 0)
{
for (int i = 0; i < n; i ++ ) printf("%.2lf\n", a[i][n]);
}
else if (t == 1) puts("Infinite group solutions");
else puts("No solution");
return 0;
}
高斯消元解异或线性方程组
输入一个包含 n 个方程 n 个未知数的异或线性方程组。
方程组中的系数和常数为 0 或 1,每个未知数的取值也为 0 或 1。
求解这个方程组。
异或线性方程组示例如下:
其中 ^ 表示异或(XOR),M[i][j] 表示第 i 个式子中 x[j] 的系数,B[i] 是第 i 个方程右端的常数,取值均为 0 或 1。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含 n+1 个整数 0 或 1,表示一个方程的 n 个系数以及等号右侧的常数。
输出格式
如果给定线性方程组存在唯一解,则输出共 n 行,其中第 i 行输出第 i 个未知数的解。
如果给定线性方程组存在多组解,则输出 Multiple sets of solutions。
如果给定线性方程组无解,则输出 No solution。
数据范围
1≤n≤100
输入样例:
3
1 1 0 1
0 1 1 0
1 0 0 1
输出样例:
1
0
0
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int n;
int a[N][N];
int gauss()
{
int c, r;
for (c = 0, r = 0; c < n; c ++ )
{
int t = r;
for (int i = r; i < n; i ++ )
if (a[i][c])
t = i;
if (!a[t][c]) continue;
for (int i = c; i <= n; i ++ ) swap(a[r][i], a[t][i]);
for (int i = r + 1; i < n; i ++ )
if (a[i][c])
for (int j = n; j >= c; j -- )
a[i][j] ^= a[r][j];
r ++ ;
}
if (r < n)
{
for (int i = r; i < n; i ++ )
if (a[i][n])
return 2;
return 1;
}
for (int i = n - 1; i >= 0; i -- )
for (int j = i + 1; j < n; j ++ )
a[i][n] ^= a[i][j] * a[j][n];
return 0;
}
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n + 1; j ++ )
cin >> a[i][j];
int t = gauss();
if (t == 0)
{
for (int i = 0; i < n; i ++ ) cout << a[i][n] << endl;
}
else if (t == 1) puts("Multiple sets of solutions");
else puts("No solution");
return 0;
}
求组合数
求组合数1
给定 n 组询问,每组询问给定两个整数 a,b,请你输出 Cbamod(109+7) 的值。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一组 a 和 b。
输出格式
共 n 行,每行输出一个询问的解。
数据范围
1≤n≤10000,
1≤b≤a≤2000
输入样例:
3
3 1
5 3
2 2
输出样例:
3
10
1
不同的a,b最多可以形成2000的平方个组合数,可以直接递推:
两个值:第一个值为在a个苹果里选红苹果,则下一个值为从a-1个苹果里再选b-1个苹果,第一个值并没有选择红苹果,则两个值都是在剩下的a-1个苹果里选择的。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 2010, mod = 1e9 + 7;
int c[N][N];
void init()
{
for (int i = 0; i < N; i ++ )
for (int j = 0; j <= i; j ++ )
if (!j) c[i][j] = 1;
else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
}
int main()
{
int n;
init();
scanf("%d", &n);
while (n -- )
{
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", c[a][b]);
}
return 0;
}
求组合数2
数据范围
1≤n≤10000,
1≤b≤a≤10^5
当a,b的范围达到1e5的级别的时候,需要预处理阶乘。
对于除法取模要用到逆元:
假如inv[b]是b的逆元:
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 100010, mod = 1e9 + 7;
int fact[N], infact[N];//前者是阶乘,后者是逆元
int qmi(int a, int k, int p)
{
int res = 1;
while (k)
{
if (k & 1) res = (LL)res * a % p;
a = (LL)a * a % p;
k >>= 1;
}
return res;
}
int main()
{
fact[0] = infact[0] = 1;
for (int i = 1; i < N; i ++ )
{
fact[i] = (LL)fact[i - 1] * i % mod;
infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod;
}
int n;
scanf("%d", &n);
while (n -- )
{
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", (LL)fact[a] * infact[b] % mod * infact[a - b] % mod);//及时取模防止一次乘爆
}
return 0;
}
求组合数3
数据范围
1≤n≤20,
1≤b≤a≤10^18,
1≤p≤10^5
a,b的范围很大,mod的值变小:
复杂度接近
O
(
p
)
O(p)
O(p)
使用卢卡斯定理:
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long LL;
int qmi(int a, int k, int p) //快速幂求逆元
{
int res = 1;
while (k)
{
if (k & 1) res = (LL)res * a % p;
a = (LL)a * a % p;
k >>= 1;
}
return res;
}
//从(a-b+1)*....a/1*2...b 分子有b项
LL C(LL a, LL b, LL p)
{
if (b > a) return 0;
LL res = 1;
LL inv=1;
for (LL i = 1, j = a; i <= b; i++, j--)
{
res = (LL)res * j % p;
inv = (LL)inv * i % p;
}
res = (LL)res * qmi(inv, p - 2, p) % p;
return res;
}
int lucas(LL a, LL b, int p)
{
if (a < p && b < p) return C(a, b, p);//小于p就直接做
return (LL)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}
int main()
{
int n;
cin >> n;
while (n--)
{
LL a, b;
int p;
cin >> a >> b >> p;
cout << lucas(a, b, p) << endl;
}
return 0;
}
求组合数 IV
数据范围
1≤b≤a≤5000
a的阶乘的个数等于a中所有质数p的倍数对p下取整的和,但是p^2 由于已经在求p的倍数的时候算过了,所以求p^2 的时候要再除p,依次类推。
最后a的阶乘就变成了这种形式:
最终的答案就是对每一个因子p,分子上的减分母上的,再把它们乘起来。
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
const int N = 5010;
int primes[N], cnt;
int sum[N];
bool st[N];
void get_primes(int n) //分解质因数里面的每一个质因数必须是一个质数
{
for (int i = 2; i <= n; i++)
{
if (!st[i]) primes[cnt++] = i;
for (int j = 0; primes[j] <= n / i; j++)
{
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
int get(int n, int p) //n这个数字是p这个数的几倍
{
int res = 0;
while (n)
{
res += n / p;
n /= p;
}
return res;
}
vector<int> mul(vector<int> a, int b)//高精度乘法
{
vector<int> c;
int t = 0;
for (int i = 0; i < a.size(); i++)
{
t += a[i] * b;
c.push_back(t % 10);
t /= 10;
}
while (t)
{
c.push_back(t % 10);
t /= 10;
}
return c;
}
int main()
{
int a, b;
cin >> a >> b;
get_primes(a);
for (int i = 0; i < cnt; i++)
{
int p = primes[i];
sum[i] = get(a, p) - get(a - b, p) - get(b, p);//每一个因子在总的式子中出现的次数
//就是这个因子在分子的指数减分母的指数。
}
vector<int> res;
res.push_back(1);
for (int i = 0; i < cnt; i++)//枚举所有的质数
for (int j = 0; j < sum[i]; j++)//枚举每一个质数的次数
res = mul(res, primes[i]);
for (int i = res.size() - 1; i >= 0; i--) printf("%d", res[i]);
puts("");
return 0;
}
扩展欧几里得算法
输入格式
第一行包含整数 n。
接下来 n 行,每行包含两个整数 ai,bi。
输出格式
输出共 n 行,对于每组 ai,bi,求出一组满足条件的 xi,yi,每组结果占一行。
本题答案不唯一,输出任意满足条件的 xi,yi 均可。
数据范围
1≤n≤105,
1≤ai,bi≤2×109
输入样例:
2
4 6
8 18
输出样例:
-1 1
-2 1
#include <iostream>
#include <algorithm>
using namespace std;
int exgcd(int a, int b, int &x, int &y)
{
if (!b)
{
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int main()
{
int n;
scanf("%d", &n);
while (n -- )
{
int a, b;
scanf("%d%d", &a, &b);
int x, y;
exgcd(a, b, x, y);
printf("%d %d\n", x, y);
}
return 0;
}
线性同余方程(扩欧的应用)
给定 n 组数据 a i , b i , m i ai,bi,mi ai,bi,mi,对于每组数求出一个 x i x_i xi,使其满足 a i × x i ≡ b i ( m o d m ) ai×xi≡bi(mod_m) ai×xi≡bi(modm),如果无解则输出 impossible。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一组数据 ai,bi,mi。
输出格式
输出共 n 行,每组数据输出一个整数表示一个满足条件的 xi,如果无解则输出 impossible。
每组数据结果占一行,结果可能不唯一,输出任意一个满足条件的结果均可。
输出答案必须在 int 范围之内。
数据范围
1
≤
n
≤
1
0
5
,
1≤n≤10^5,
1≤n≤105,
1
≤
a
i
,
b
i
,
m
i
≤
2
×
1
0
9
1≤ai,bi,mi≤2×10^9
1≤ai,bi,mi≤2×109
输入样例:
2
2 3 6
4 3 5
输出样例:
impossible
-3
#include<bits/stdc++.h>
using namespace std;
int n, x, y;
using LL = long long ;
int exgcd(int a, int b, int &x, int &y)
{
if (!b)
{
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, x, y);
int z = x;
x = y;
y = z - a / b * y;
return d;
}
int main()
{
cin >> n;
while (n --)
{
int a, b, m;
cin >> a >> b >> m;
int d = exgcd(a, m, x, y);//次方程的x 为 gcd的解,y=-y 。
if (b % d) puts("impossible");//如果b不是gcd的倍数就无解,看证明
else
{ //如果b是gcd的倍数,那么解得的 x 是满足方程最小的根
//因为满足扩欧方程的x,y一定是gcd的倍数,而此时解gcd得方程的x一定是最小的根
x = (LL)x * b / d % m;//最小的x乘以倍数即为x的解
cout << x << endl;
}
}
return 0;
}
注意:对于这样一个式子:
a
⋅
x
+
b
⋅
y
=
d
得
到
的
x
,
y
并
不
是
唯
一
解
a·x+b·y = d得到的x,y并不是唯一解
a⋅x+b⋅y=d得到的x,y并不是唯一解
显
然
a
⋅
(
x
−
b
d
)
+
b
⋅
(
y
+
a
d
)
=
d
也
是
成
立
的
显然a·(x-\dfrac{b}{d})+b·(y+\dfrac{a}{d})=d也是成立的
显然a⋅(x−db)+b⋅(y+da)=d也是成立的
−
a
⋅
b
d
+
a
⋅
b
d
=
0
-\dfrac{a·b}{d}+\dfrac{a·b}{d}=0
−da⋅b+da⋅b=0
所
以
通
解
x
=
x
0
+
k
b
d
所以通解x=x_0+k\dfrac{b}{d}
所以通解x=x0+kdb
所
以
通
解
y
=
y
0
−
k
a
d
所以通解y=y_0-k\dfrac{a}{d}
所以通解y=y0−kda
求mod非质数p的逆元(扩欧的应用)
int exgcd(int a, int b, int &x, int &y) {
if (b == 0) {
x = 1;
y = 0;
return a;
} else {
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
}
int main()
{ int a;
cin >> a >> b; // b = mod;
int x, y,;
exgcd(a, b, x, y);
if (x < 0) x += mod;//第一个非负的x即为a在mod b 意义下的逆元
return x;
}
容斥原理(能被整除的数)
给定一个整数 n 和 m 个不同的质数 p1,p2,…,pm。
请你求出 1∼n 中能被 p1,p2,…,pm 中的至少一个数整除的整数有多少个。
输入格式
第一行包含整数 n 和 m。
第二行包含 m 个质数。
输出格式
输出一个整数,表示满足条件的整数的个数。
数据范围
1≤m≤16,
1≤n,pi≤109
输入样例:
10 2
2 3
输出样例:
7
样例解释:
n = 10, p1=2,p2=3, 求1-10中能满足能整除p1或p2的个数, 即2,3,4,6,8,9,10,共7个。
根据韦恩图得出的容斥原理:
以题目样例为例
S1={2,4,6,8,10},S2={3,6,9},S1⋂S2={6},故S1⋃S2={2,3,4,6,8,9,10}
实现:
(1)Si如何表示:|Si|=n/pi
这道题不关注最终答案的集合是谁,所以只需要求出最终集合里的元素个数:如果对于p1…pk,满足pk<n ,(如果pk>n没有意义), 如果n%2==0, n/pk 即为n中pk倍数的个数,如果
n
n%2 !=0
n,那么下取整[n/pk]为最多的n中pk倍数的个数。
(2)|Sm⋂Sn|如何表示:|Sm⋂Sn|=n/(pm*pn)
因为p为质数,这些质数的乘积就是他们的最小公倍数,n除这个最小公倍数就是交集的大小:举个例子,|S1⋂S2|=
n
/
(
p
1
∗
p
2
)
=
10
/
(
2
∗
3
)
=
1
n/(p1∗p2)=10/(2∗3)=1
n/(p1∗p2)=10/(2∗3)=1,所以样例答案为 5 + 3 - 1 =7
(3)表示集合的状态:二进制
假设存在一个质数p=4,它的二进制为1101。那么就可以表示选中集合为S1,S2,S4三个集合——即以某一位数的1与0代表选取与否。然后计数选取集合的个数,根据容斥原理的公式,我们通过个数的奇偶判断某一项的符号
(
−
1
)
3
−
1
(−1)^{3-1}
(−1)3−1
=
=
=
+
1
+1
+1,那么对于这一项对答案的贡献就是:res+=n/(p1∗p2∗p4)。我们可以枚举0000到1111的每一个状态来求得每一项对答案的贡献即为最终答案。
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 20;
int p[N], n, m;
int main()
{
cin >> n >> m;
for (int i = 0; i < m; i++) cin >> p[i];
int res = 0;
//枚举从1 到 1111...(m个1)-(2^m位) 的每一个集合状态
for (int i = 1; i < 1 << m; i++) //i<1<<m就是枚举到2^m-1,
{//容斥原理复杂度2^n,减去一个集合不选的情况2^n-1
int t = 1; //选中集合对应质数的乘积
int s = 0; //选中的集合数量
//枚举当前状态的每一位
for (int j = 0; j < m; j++)
{
//选中一个集合
if (i >> j & 1) //i的二进制的第j位是不是1
{
//乘积大于n, 则n/t = 0, 没有意义,跳出这轮循环
if ((LL)t * p[j] > n)
{
t = -1;
break;
}
s++; //每有一个1,集合数量+1
t *= p[j];
}
}
if (t == -1) continue;
if (s % 2 != 0)
res += n / t; //选中奇数个集合, 则系数应该是1, n/t为当前这种状态的集合数量
else
res -= n / t; //反之则为 -1
}
cout << res << endl;
return 0;
}
中国剩余定理
表达整数的奇怪方式
#include<iostream>
using namespace std;
typedef long long LL;//数据范围比较大,所以用LL来存储
LL exgcd(LL a,LL b,LL &x,LL &y)
{
if(!b)
{
x=1,y=0;
return a;
}
LL d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
int main()
{
int n;
LL a1,m1;
cin>>n>>a1>>m1;
LL x=0;
for(int i=1;i<n;i++)
{
LL a2,m2;
cin>>a2>>m2;
LL k1,k2;
LL d=exgcd(a1,a2,k1,k2);
if((m2-m1)%d)
{
x=-1;
break;
}
k1*=(m2-m1)/d;
//因为此时k1是k1*a1+k2*a2=d的解,所以要乘上(m2-m1)/d的倍数大小
LL t=abs(a2/d);
k1=(k1%t+t)%t;
//数据比较极端,所以只求k的最小正整数解
m1=k1*a1+m1;
//m1在被赋值之后的值为当前"x"的值,此时赋值是为了方便下一轮的继续使用
a1=abs(a1*a2/d);
//循环结束时a1的值为当前所有的a1,a2,……an中的最小公倍数
}
if(x!=-1)
x=(m1%a1+a1)%a1;
//当循环结束时,此时的值应该与最小公倍数取模,以求得最小正整数解
printf("%lld\n",x);
return 0;
}