第十二届蓝桥杯Java省赛A组试题:异或数列

【题目描述】
初始时,Alice和Bob分别有一个整数a和b,有一个给定的长度为n的数列。a和b的初始值均为0。Alice和Bob轮流操作,Alice先手,每步可以从两个选项中选一种:
选项1:从数列中选一个X;给Alice的数异或上。
选项2:从数列中选一个X;给Bob的数异或上。
每个数Xi只能用一次,当每个数都被用过一遍后,游戏结束。拥有数大的一方获胜,双方数字相同即平局。
双方都足够聪明,都采用最优策略,问谁能获胜?

【输入格式】
每个评测用例包含多组询问。询问之间彼此独立。
输入的第一行包含一个整数T,表示询问数。
接下来T行每行包含一组询问。其中第i行的第一个整数n;表示数列长度,随后n个整数X1, X2,… Xm表示数列中的每个数。

【输出格式】
输出T行,依次对应每组询问的答案。
每行包含一个整数1、0或-1分别表示Alice胜、平局或败。

【样例输入】
4
1 1
1 0
2 2 1
7 992438 1006399 781139 985280 4729 872779 563580

【样例输出】
1
0
1
1

【评测用例规模与约定】
对于所有评测用例,1 ≤ T ≤ 200000,1 ≤ ∑Ti=1 ni ≤ 200000,0 ≤ Xi < 220。


【解题思路】
借鉴:该博客

首先明确,为什么给定一个数列,游戏的结局是确定的呢?
A赢的意思是:A能采取某种策略,不管B作何应对,A保证自己一定能赢;
B赢的意思是:B能采取某种策略,不管A作何应对,B保证自己一定能赢;
对于异或:0^X ——保持X ; 1^X——翻转X;
偶数次翻转将回到原来的状态,即与偶数个1异或将保持不变。

最后两个结果数A^B = a^b^sum ,其中sum为给定数列所有数的异或和,sum = X1^X2^...Xni,所以如果是平局(即A = B),则A^B=0(即 sum = 0)。

现在考虑sum != 0时,因为是按位异或,并且最后比较A和B两值的大小,所以要从二进制的最高位开始比较,如果最高位相等,比较次高位,以此往下比较,直到可以判断出结果。

用一个int型数组res[i]表示二进制的第i位上1的个数,如果某一位的res[i]为奇数,即该位上有奇数个1与a和b异或,比如有5个1,因为0不会改变状态,其(a,b)的状态转移过程一定是:(0,0)->翻转->平局->翻转->平局->翻转,也就是说在平局不管是(0,0)或(1,1)的基础上,Alice 和 Bob谁拥有最后一次翻转权(也就是最后一个1),谁就赢得游戏!

那怎么让最后一个1轮到自己身上?就要用到0了,因为0不会改变数的大小,但相当于让自己“轮空一次”。还是考虑上面5个1的情况:如果在最后一次翻转之前(不管在什么位置)插入偶数个0,则最后一次1的翻转权来到了先手 Alice,则先手胜出;如果在最后一次翻转之前(不管在什么位置)插入奇数个0,则最后一次1的翻转权来到了后手 Bob,则后手胜出。

因为Alice和Bob都“足够聪明”,面对奇数个1,Bob一定要去“ 抢0 ”,自己才有一线生机,而Alice也要以“ 抢0 ”来作应对,让最后一次翻转权回到自己手里,因为0的个数有限,最后决定胜负的就是0个数的奇偶性。

因此,如果某一位的res[i]为偶数,则游戏结果的这一位一定相等,考虑下一位 ;如果第i位上1的个数为 1,则一定是先手赢(输出:“1”);如果第i位上1的个数为大于1的奇数,0的个数为偶数,则先手赢(输出:“1”);如果第i位上1的个数为大于1的奇数,0的个数为奇数,则后手赢(输出:“-1”)。


Java代码:

import java.util.ArrayList;
import java.util.Scanner;

public class Main {
    
    
    public static void main(String[] args) {
    
    
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        //该ArrayList用来暂存每次询问结果,待最后一次询问结束再统一输出
        ArrayList<Integer> res = new ArrayList<>();
        //n次询问
        for (int i = 0; i < n; i++) {
    
    
            int[] weis = new int[24]; //该数组用于存储二进制形式下每一位1的数量
            int m = scanner.nextInt();
            //长度为m的数列转为二进制并处理
            for (int j = 0; j < m; j++) {
    
    
                long l = scanner.nextLong();
                String string = Long.toBinaryString(l); //转为二进制
                String reStr = new StringBuilder(string).reverse().toString(); //反转,即高低位反转,便于存储与后面处理
                //判断二进制形式下每一位是否为1
                for (int k = 0; k < reStr.length(); k++) {
    
    
                    if (reStr.charAt(k) == '1') weis[k]++;
                }
            }
            //对每次询问后每一位中1的个数判断从而得到结果,并将结果先暂时存储在ArrayList-res中
            for (int j = weis.length - 1; j >= 0; j--) {
    
    
                if (weis[j] % 2 == 0 && j == 0) {
    
    
                    res.add(0);
                    break;
                }else if (weis[j] % 2 == 0){
    
    
                    continue;
                }
                if (weis[j] == 1){
    
    
                    res.add(1);
                    break;
                }else if (weis[j] % 2 == 1 && (m - weis[j]) % 2 == 0){
    
    
                    res.add(1);
                    break;
                }else if (weis[j] % 2 == 1 && (m - weis[j]) % 2 == 1){
    
    
                    res.add(-1);
                    break;
                }
            }
        }
        //输出结果
        for (int x : res){
    
    
            System.out.println(x);
        }
    }
}

猜你喜欢

转载自blog.csdn.net/m0_46653805/article/details/123666722