素(质)数
1)试除法判断素数
boolean isPrime(int n){
if( n == 1) return false;
for(int i = 2 ; i <= n/i ; i ++){
if( n % i == 0 ) return false;
}
return true;
}
2)分解质因数
- 1)分解 n 的质因数
void devide(int n ){
for(int i = 2 ; i <= n / i ; i ++){
if(n % i == 0 ){
int cnt = 0;
while( n % i == 0){
cnt++;
n /= i;
}
System.out.println(i +" "+ cnt);
}
}
if( n > 1) System.out.println(n +" "+ 1);
}
- 2)分解 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;
}
}
for(int i = 0 ; i < cnt ; i ++ )
{
int p = primes[i];
int count = 0;
for(int j = n ; j ; j /= p ) count += j / p;
cout << p <<' ' << count << endl;
}
筛质数
int primes(int n ){
int[] primes = new int[n];
boolean[] st = new boolean[n+1];
int cnt = 0;
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;
}
}
return cnt;
}
筛区间[L ,R]之间的质数
1)找出1-50000(sqrt(Integer.MAX))之间的所有质因子
2)对于1-50000中的每个质数 p ,将[L ,R]中所有p的倍数筛掉(至少2倍)
typedef long long LL;
const int N = 1000010;
int primes[N] , cnt;
bool st[N];
void init(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 main()
{
cin >> l >> r ;
init(50000);
memset(st ,0 ,sizeof st);
for(int i = 0 ; i < cnt ; i ++)
{
LL p = primes[i];
for(LL j = max( p * 2 , (l + p - 1) / p * p ) ; j <= r ; j += p )
st[j - l] = true;
}
cnt = 0;
for(int i = 0 ; i <= r - l ; i ++ )
if( !st[i] && i + l >= 2) primes[cnt++] = i + l;
return 0;
}
约(因)数
1)试除法
void divisor(int n){
ArrayList<Integer> left = new ArrayList<>();
ArrayList<Integer> right =new ArrayList<>();
for(int i = 1 ; i <= n / i ;i ++){
if( n % i == 0 ){
left.add(i);
if( i != n / i ) right.add( n / i);
}
}
for(Integer i : left)System.out.print(i+" ");
for(int i = right.size() - 1 ; i >= 0 ; i--)System.out.print(right.get(i)+" ");
}
2)约数个数
n 个正整数乘积的约数个数
公式:
n=p1^a1 * p2^a2 * p3^a3 … pk^ak
约数个数:(a₁+1)(a₂+1)(a₃+1)…(ak+1)
import java.util.*;
public class Main{
static int mod = (int)1e9 + 7;
static HashMap<Integer,Integer> map = new HashMap<>();
static void get_dividsor(int n ){
for(int i = 2 ; i <= n / i ;i ++){
if(n % i == 0){
int cnt = 0;
while( n % i == 0){
cnt++;
n /= i;
}
map.put(i , map.getOrDefault(i , 0) + cnt);
}
}
if(n > 1)map.put(n , map.getOrDefault(n , 0) + 1);
}
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
while( n -- > 0){
int x = sc.nextInt();
get_dividsor(x);
}
long ans = 1;
for(int cnt : map.values() ){
ans = ans * (cnt+1) % mod; //组合的思想 ,比如cnt个2 , 可以选择0 ~ cnt 个 2
}
System.out.println(ans);
}
}
3)约数之和
n个正整数的乘积的约数之和
公式:
n=p1^a1 * p2^a2 * p3^a3 … pk^ak
约数和:f(n)=(p1^0 + p1^1 + p1^2 +…p1^a1 )(p2^0 +p2^1 +p2^2 +…p2^a2 )…(pk^0 +pk^1 +pk^2 +…pk^ak )
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
HashMap<Integer , Integer> map = new HashMap<>();
int mod = (int)1e9 +7;
while(n-- > 0){
int x = sc.nextInt();
for(int i = 2; i <= x/i ;i ++){
while(x % i == 0){
map.put(i , map.getOrDefault(i , 0) + 1);
x /= i;
}
}
if( x > 1) map.put(x , map.getOrDefault(x , 0) + 1);
}
long res = 1;
for(Map.Entry<Integer,Integer> entry : map.entrySet()){
int p = entry.getKey() , a = entry.getValue();
// 1 + p^1 + p^2 +...+p^a
// 1 + p(1 + p + ...+p^a-1 )
// 1 + p(1 + p(1 + p + .. + p^a-2)
// ...
// 1 + p(1 + p(1 + p(1 +P((...(1+p) ))))) a-1个括号
//从最内层往外层算
long t = 1;
for(int i = 1 ; i <= a ; i++){
t = (t*p+1) % mod;
};
res = res * t % mod;
}
System.out.println(res);
}
}
4)最大公约数
欧几里得算法(辗转相除法)
int gcd(int a ,int b){
return b == 0 ? a : gcd(b , a % b );
}
4)最小公倍数
欧几里得算法(辗转相除法)
int lcm(int a ,int b){
return a * b / gcd(a , b );
}
欧拉函数
1∼N 中与 N 互质的数的个数被称为欧拉函数,记为 ϕ(N)。
若在算数基本定理中,则:
- 若 n为质数,则 φ(i) = n - 1 ,因为 1~ n-1中每个数都和 n 互质
- p为质数,已知 φ(i) = i * x ,求 φ(p * i) :
- 若 p 是 i 的约数,则 φ(p * i) = p * i * x = p * φ(i)
因为 p 是 i 的约数 ,所以在求 φ(i) 的公式中已经乘了 (p - 1) / p ,虽然 p 也是 p * i 的约数,但在求某个数的欧拉函数的公式中,只看约数,而不看约数的次数。 - 若 p 不是 i 的约数,则
φ(i * p) = i * p * x * (p - 1) / p = i * x * (p - 1) = φ(i) * (p - 1)
- 若 p 是 i 的约数,则 φ(p * i) = p * i * x = p * φ(i)
1)求某个数 的欧拉函数
int euler(int n ){
int res = n;
for(int i = 2 ; i <= n / i;i ++ ){
if( n % i == 0 ){
while( n % i == 0){
n /= i ;
}
res = res / i * (i - 1); //这里要先除 ,因为(i-1)/ i 不能整除
}
}
if(n > 1) res = res / n *(n - 1);
return res;
}
2)求 1 - n 之间的所有的数的欧拉函数之和
long get_euler(int n){
int[] primes = new int[n+1];
int[] phi = new int[n+1];
int idx = 0;
boolean[] st = new boolean[n+1];
for(int i = 2 ;i <= n ;i ++ ){
if(!st[i]){
primes[idx++] = i;
phi[i] = i-1;
}
for(int j = 0 ; primes[j] <= n/i ;j ++){
st[i*primes[j]] = true;
if( i % primes[j] == 0 ){
phi[i * primes[j] ] = phi[i] * primes[j];
break;
}
phi[i * primes[j]] = phi[i] * (primes[j]-1);
}
}
long res = 1;//phi[1] = 1
for(int i = 2 ; i<=n;i++)res += phi[i];
return res;
}
逆元
1)快速幂
long qmi(long a ,long k ,long p){
long res = 1;
while( k != 0 ){
if( (k & 1) == 1 ) res = res * a % p;
k = k >> 1;
a = a * a % p;
}
return res ;
}
快速幂求逆元
若 p 为质数,费马小定理 ,即 ,所以 b 的逆元为
b 不能为 p的倍数 ,否则
x = qmi(b,p-2,p) //调用上面求快速幂的函数即可
若 p 不是质数,可以用扩展欧几里得算法求逆元:
a有逆元的充要条件是a与p互质,所以gcd(a, p) = 1
假设a的逆元为x,那么有a * x ≡ 1 (mod p)
等价:ax + py = 1
exgcd(a, p, x, y)
扩展欧几里得
扩展欧几里得算法用于求解 ax + by = gcd(a , b)的解
当 b = 0 时 , ax + by = a ,故 x = 1 , y = 0
当 b ≠ 0 时 ,
因为 gcd(a , b) = gcd(b , a % b)
而
故而
static int x ,y; //a , b互质时 , x 即为 a 的逆元
static int exgcd(int a ,int b ){
if(b == 0){
x = 1; y = 0;
return a;
}
int d = exgcd(b , a % b);
int t = y;
y = x - a / b * y;
x = t ;
return d;
}
2)线性同余
扩展欧几里得可以求解 ax + by = gcd(a , b)
---> -->
即
使用扩展欧几里得求得
1)裴蜀定理( Bezout ),当 ,原式有解,且 ,故 ( % m 是要求 x 也在 1 - m直之间)
2)当 ,原式无解
public class Main{
static int x , y;
static int exgcd(int a ,int b){
if(b == 0){
x = 1; y = 0;
return a;
}
int d = exgcd(b , a % b);
int t = y;
y = x - a/b *y;
x = t;
return d;
}
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
while(n -- > 0){
int a = sc.nextInt() , b = sc.nextInt() , m = sc.nextInt();
int d = exgcd(a ,m);
if( b % d == 0) System.out.println( (long)x * b / d % m );
else System.out.println("impossible");
}
}
}
3 欧拉定理
若 a , n为正整数,且 a , n互质
中国剩余定理
以两个式子 x = m1 (mod) a1 ,x = m2 (mod) a2开始推导
即
加上 ,得
即
则
可以 把 继续和后面的式子合并,最后的形式就是一个样的式子
即答案
注意的是:在每合并两个式子的时候都需要判断是否满足
import java.util.*;
public class Main{
static long x , y;
static long exgcd(long a ,long b){
if(b == 0){
x = 1 ; y = 0;
return a;
}
long d = exgcd(b , a % b);
long t = y;
y = x - a / b * y;
x = t;
return d;
}
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
long a1 = sc.nextLong() ,m1 = sc.nextLong();
boolean has_answer = true;
while(--n > 0){
long a2 = sc.nextLong() , m2 = sc.nextLong();
long d = exgcd(a1 , -a2);
if( (m2 - m1) % d != 0 ){
has_answer = false;
break;
}
long t = (m2 - m1) / d;
long mod = Math.abs( a2 / d ); // a2 / d不知道正负
x = (x * t % mod + mod) % mod; // k1 = k1 + k*a2/d,模的是 abs(a2 / d),让 k1尽可能的小
m1 = x * a1 + m1; //新的 X0
a1 = Math.abs(a1 / d *a2); //不知道正负,所以取绝对值
}
if(!has_answer) System.out.println("-1");
else System.out.println( (m1% a1 + a1) % a1 ); //取的是正数
}
}
组合数
1)通过组合的思想求
数据范围特点:n比较大,a , b不大
1 ≤ n ≤ 10000,
1 ≤ b ≤ a ≤ 2000
static int N = 2010;
static int[][] g = new int[N][N];
static int mod = (int)1e9 + 7;
static{
for(int i = 0 ; i < N ; i ++)
for(int j = 0 ; j <= i ;j ++)
if( j == 0 ) g[i][j] = 1;
else g[i][j] = (g[i-1][j-1] + g[i-1][j]) % mod;
}
2)使用公式求:
数据范围特点:n比较大,a,b比较大,对 1e9 + 7去模
1 ≤ n ≤ 10000,
1 ≤ b ≤ a ≤ 100000
主要过程是先预处理出 1-100000的阶乘,阶乘的逆元,然后使用公式计算
static int qmi(int a ,int k ,int p){
long res = 1;
while( k!= 0 ){
if( (k&1) == 1 ) res = res * a % mod;
k = k >> 1;
a = (int)((long)a * a % mod);
}
return (int)res;
}
static int N = 100010;
static long[] fact = new long[N] , infact = new long[N];
static int mod = (int)1e9 + 7;
static{
fact[0] = infact[0] = 1;
for(int i = 1 ; i < N ; i ++ ){
fact[i] = fact[i-1] * i % mod;
infact[i] = infact[i-1] * qmi( i , mod-2 , mod) % mod ; // mod 是质数,费马小定理
}
}
//最后:
ans = fact[a] * infact[a - b] % mod * infact[b] % mod
3)Lucas定理求
数据范围特点:n 很小,a , b非常大,p为质数(比a还小)
1 ≤ n ≤ 20,
1 ≤ b ≤ a ≤ 10^18,
1 ≤ p ≤ 10^5,
lucas定理:
当 a 或者 b 大于 p时,递归求解,当 a < p && b < p时,直接使用公式求解
static long qmi(long a ,long k ,long p){
long res = 1;
while( k != 0){
if( (k&1) == 1 ) res = res * a % p;
k >>= 1;
a = a * a % p;
}
return res;
}
static long C(long a ,long b ,long p){
long res = 1;
for(long i = a , j = 1; j <= b ; i -- , j ++ ){
res = res * i % p;
res = res * qmi(j , p-2 , p) % p;
}
return res;
}
static long lucas(long a , long b ,long p){
if( a < p && b < p ) return C(a , b , p);
return C(a % p, b % p , p) * lucas(a / p , b / p , p) % p;
}
ans = lucas(a , b ,p)
4)质数线性筛法 + 高精度乘法求 ,没有取模
数据范围特点:不对结果取模,结果非常大
1 ≤ b ≤ a ≤ 5000
import java.util.*;
public class Main{
static int N = 5010;
static int[] primes = new int[N];
static int[] sum = new int[N];
static boolean[] st = new boolean[N];
static int cnt = 0;
static 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;
}
}
}
static int get(int x , int p){
int res = 0;
while( x != 0){
res += x / p;
x /= p;
}
return res;
}
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int a = sc.nextInt() , b = sc.nextInt();
get_primes(a);
for(int i = 0 ; i < cnt ;i ++){
int p = primes[i];
sum[i] = get(a , p) - get(b , p) - get(a - b , p);
}
ArrayList<Integer> res = new ArrayList<>();
res.add(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 --)
System.out.print( res.get(i) );
}
static ArrayList<Integer> mul(ArrayList<Integer> A ,int b){
ArrayList<Integer> res = new ArrayList<>();
int t = 0;
for(int i = 0; i < A.size() ; i ++){
t = A.get(i) * b + t;
res.add( t % 10 );
t /= 10;
}
while( t != 0){
res.add( t % 10 );
t /= 10;
}
return res;
}
}
5)卡特兰数
变形:
- 满足 递推式: f(n) = f(1) * f(n-1) + f(2) * f(n-2) + ....
-
有一种性质:任意前缀中某种东西 >= 另外一种东西
import java.util.*;
public class Main{
static long qmi(long a ,long k , int p){
long res = 1;
while(k!=0){
if( (k&1) == 1 ) res = res * a % p;
k = k >> 1;
a = a * a % p;
}
return res;
}
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int mod = (int)1e9 + 7;
int a = 2*n , b = n;
long ans = 1;
for(int i = a , j = 1; i > a-b;i-- ,j++){
ans = ans * i % mod;
ans = ans * qmi(j , mod-2 ,mod) % mod;
}
ans = ans * qmi(n + 1 , mod-2 ,mod) % mod;
System.out.println(ans);
}
}
高斯消元
线性方程组
import java.util.Scanner;
public class Main{
static int n , N = 110;
static double[][] g = new double[N][N];
static double eps = 1e-6;
static int gauss(){
int c , r;
for(c = 0 , r = 0 ; c < n ; c ++){
//找主元
int t = r;
for(int i = r ; i < n ;i ++)
if( Math.abs(g[i][c]) > Math.abs(g[t][c]) )
t = i;
if( Math.abs( g[t][c] ) < eps ) continue;
//交换
for(int i = c ; i <= n ;i ++) swap(t ,i ,r ,i);
//归一化
for(int i = n ; i >= c ; i --) g[r][i] /= g[r][c];
//消元
for(int i = r+1 ; i < n ;i ++)
if( Math.abs( g[i][c] ) > eps )
for(int j = n ; j >= c ; j --)
g[i][j] -= g[i][c] * g[r][j];
r++;
}
if( r < n){
for(int i = r ; i < n ;i ++)
if( Math.abs(g[i][n]) > eps ) return 2; //无解
return 1;//无限解
}
//求解
for(int i = n - 1 ; i >= 0 ;i --)
for(int j = i+1 ; j < n ; j ++ )
g[i][n] -= g[i][j] * g[j][n];
return 0;
}
static void swap(int x1 ,int y1 ,int x2 ,int y2){
double t = g[x1][y1];
g[x1][y1] = g[x2][y2];
g[x2][y2] = t;
}
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
for(int i = 0 ; i < n ;i ++)
for(int j = 0 ; j <= n ;j ++)
g[i][j] = sc.nextDouble();
int ans = gauss();
if(ans == 2) System.out.println("No solution");
else if(ans == 1) System.out.println("Infinite group solutions");
else for(int i = 0 ; i < n ;i ++) System.out.println( String.format("%.2f" ,g[i][n] ) );
}
}
异或方程组
import java.util.*;
public class Main{
static int n , N = 110;
static int[][] a = new int[N][N];
static int gauss(){
int r , c;
//从第一列开始遍历
for(r = c = 0 ; c < n ; c ++){
int t = r; //选择第 c列不为0的行
for(int i = r ; i < n ;i ++){
if( a[i][c] != 0 )
t = i;
}
//表示该列所有行(r及以下)都为0
if( a[t][c] == 0 ) continue;
//把不为0的行和当前行交换
for(int i = c ; i < n +1 ; i ++ ) swap(r , i , t , 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){
for(int i = r; i < n ;i ++)
if(a[i][n] != 0)
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]; // j表示的是第i行第j个数
// 对应的为1的行在第j列
}
}
return 0 ;
}
static void swap(int x1 ,int y1 ,int x2 ,int y2){
int t = a[x1][y1];
a[x1][y1] = a[x2][y2];
a[x2][y2] = t ;
}
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
for(int i = 0 ; i < n ;i ++)
for(int j = 0 ; j < n + 1 ;j ++)
a[i][j] = sc.nextInt();
int res = gauss();
if( res == 0 ){
for(int i = 0 ; i < n ; i ++ ) System.out.println(a[i][n]);
}else if( res == 1 ){
System.out.println("Multiple sets of solutions");
}else{
System.out.println("No solution");
}
}
}
容斥原理
容斥原理: ,那么根据容斥原理,所有数的个数为各个集合的并集, 计算公式如下:
实现思路
1)实际上并不需要知道每个集合里面的具体的元素是什么,只需要知道集合的大小,大小为 。
2)为质数,这些数的乘积就是他们的最小公倍数,所以交集的大小就是 n除以它们的乘积 ,交集的大小为
3)使用二进制作为集合状态的表示,比如有 4 个质数, m = 4,所以需要 4 位二进制进行表示,每个二进制位表示集合是否被选中,例如:,表示的 集合被选中,这个集合的元素个数为 ,在公式中,系数为 ,k为选中的集合个数 ,即选中奇数个集合前面的系数为1,偶数个集合系数为 -1。
实现代码:
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
int[] p = new int[m];
for(int i = 0; i < m ;i ++)p[i] = sc.nextInt();
int res = 0;
//枚举从00001到 11111(m个1)的每一个集合状态,(至少选中一个集合)
for(int i = 1 ; i < 1 << m ; i ++){ //
int t = 1; //选中的集合对应的乘积
int s = 0;//选中的集合数量
//枚举当前状态的每一位
for(int j = 0; j < m ; j ++){
//第j个集合被选中了
if( (( i>>j ) & 1 ) == 1 ){
//乘积大于n , 则 n/t = 0 ,跳出这轮循环
if( (long) t * p[j] > n ){
t = -1;
break;
}
s++;
t *= p[j];
}
}
if(t == -1) continue;
if( (s&1) == 1 ) res += n/t; //选中奇数个集合,系数为1
else res -= n/t; //偶数个集合,系数为-1
}
System.out.println(res);
}
}
博弈论
若一个游戏满足:
1,由两名玩家交替行动
2,在游戏进行的任意时刻,可以执行的合法行动与轮到哪位玩家无关
3,不能行动的玩家判负
则称该游戏为一个公平组合游戏
题目描述
给定n堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
例如:有两堆石子,第一堆有2个,第二堆有3个,先手必胜。
操作步骤:
- 先手从第二堆拿走1个,此时第一堆和第二堆数目相同
- 无论后手怎么拿,先手都在另外一堆石子中取走相同数量的石子即可。
必胜状态 和 必胜状态
- 必胜状态:先手进行某一个操作,留给后手一个必败状态时,对于先手来说就是一个必胜状态。
即:先手可以走到某一个必败状态 - 必败状态:先手无论如何操作,留给后手都是一个必胜状态,对于先手来说是是一个必败状态。
即:先手走不到任何一个必败状态
结论: 假设n堆石子,石子数目分别是a1,a2,…,an,如果a1 ⊕ a2 ⊕…⊕ an≠0a1 ⊕ a2 ⊕…⊕ an≠0,先手必胜;否则先手必败。
证明:
- 操作到最后时,每堆石子数都是0 , 0⊕ 0...⊕ 0 = 0
- 在操作过程中,如果 a1 ⊕ a2 ... ⊕ an ≠ 0,那么玩家必然可以通过拿走某一堆石子将异或结果变为0
证明:不妨设x的二进制表示中最高一位1在第k位,那么在 a1,a2,…,an中,必然有一个数ai,它的第k为时1,且 ai ⊕ x < ai,那么从第i堆石子中拿走( ai − ai ⊕ x ) 个石子,第i堆石子还剩 ai - ( ai − ai ⊕ x ) = ai ⊕ x,此时 a1 ⊕ a2 ⊕ … ⊕ ai ⊕ x ⊕…⊕ an = x ⊕ x = 0。 - 在操作过程中,如果 a1 ⊕ a2 ⊕…⊕ an = 0,那么无论玩家怎么拿,必然会导致最终异或结果不为 0。
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int ans = 0;
while(n-- > 0){
ans ^= sc.nextInt();
}
if(ans == 0) System.out.println("No");
else System.out.println("Yes");
}
}