基础数论

素(质)数

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 left = new ArrayList<>();
        ArrayList 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 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 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 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)

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 ); //取的是正数
    }
}
CRT.png


组合数

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 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 mul(ArrayList A ,int b){
        ArrayList 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)卡特兰数

变形:

  1. 满足 递推式: f(n) = f(1) * f(n-1) + f(2) * f(n-2) + ....
  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. 先手从第二堆拿走1个,此时第一堆和第二堆数目相同
  2. 无论后手怎么拿,先手都在另外一堆石子中取走相同数量的石子即可。

必胜状态必胜状态

  1. 必胜状态:先手进行某一个操作,留给后手一个必败状态时,对于先手来说就是一个必胜状态。
    即:先手可以走到某一个必败状态
  2. 必败状态:先手无论如何操作,留给后手都是一个必胜状态,对于先手来说是是一个必败状态。
    即:先手走不到任何一个必败状态

结论: 假设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");
    }
}

你可能感兴趣的:(基础数论)