折半搜索(中途相遇法) + 暴力枚举:PIPI的炼金术

16 篇文章 1 订阅

折半搜索(中途相遇法) + 暴力枚举:PIPI的炼金术

文章目录

问题:

在这里插入图片描述
在这里插入图片描述

思路:

首先我们要把问题进行一个转化。根据题意,我们把若干材料炼金成功,只需这些材料字符串各个字符出现个数之和为偶数就行,而材料字符串全都是由小写字母组成,小写字母a-z可以对应数字0-25。因此我们可以考虑如下操作:我们将每个字符串中字符的出现次数是偶数还是奇数记录下来,用二维数组alphabet保存,alphabet[i][j]=0表示第i个字符串的字母j出现次数为偶数,alphabet[i][j]=1表示第i个字符串的字母j出现次数为奇数。然后我们可以将每个材料字符串26个字母的出现个数是偶数还是奇数的信息转化为一个26位的二进制数字,若某位为1,表示该位置对应字母的出现个数为奇数,反之为偶数。那么n个字符串也就变成了n个数字,所以问题就变成了:对于n个数字,你可以从中任取若干个(但数量必须大于0)数字,把他们全部异或起来,其值若为0,即是一种合法的方案(因为若要组合之后的字符串字母出现次数为偶数,则被组合的原始字符串中该字母出现次数要么都为奇数,要么都为偶数,这正好对应了相应2进制位中1 1和0 0的情况,而相同的异或为0,正好对应组合后字母出现次数为偶数,然后若异或结果为值0,则是所有二进制位都为0,对应所有字符出现次数为偶数,即题中的条件)。
进行完转化后,就到了统计方案个数的问题。题中可以组合任意字符串,对于每个字符串,显然,都有选或不选两种选择,因此很容易想到O(2^n)的暴力枚举法,从000…001枚举到111…111,然后通过位运算和移位将对应选中的字符串异或起来。但是本题n的大小有35,时间复杂度过不去。这时候我们就需要用折半搜索法优化时间复杂度。
折半搜索也叫中途相遇法。简单来说就是处理前一半,把结果储存起来,再处理后一半,然后匹配前一半存储的结果。也就是在枚举后一半时利用前一半产生的结果,避免重复计算。
对于此题,就是先枚举一半,从1(对应000…001)枚举到2 ^ (n / 2 + 1) - 1(对应000…111…111),若异或结果为0,则结果ans++,表示找到一种方案,无论异或结果如何,都把计算出该异或结果的个数用哈希表保存。然后我们枚举另一半,从2 ^ (n / 2 + 1)(对应000…100…000)枚举到后半段全1(即对应111…111000…000),注意我们枚举后半段时步长为2 ^ (n/2 + 1),即我们枚举后半段,表示枚举后半段的字符串,这时我们完全不考虑前半段的字符串,即枚举时前半部分的二进制位都为0。若异或结果为0,则结果ans++,表示找到一种方案,无论异或结果如何,都去查询哈希表,若哈希表中保存了当前计算出的异或值,则表示前半段枚举中出现了和当前字符奇偶分布情况一模一样的,那么我们需要用ans加上哈希表中存储的方案数,因为之前保存的方案可以组合当前方案,从而到达题目要求。(因为若两个组合字符串字符奇偶分布一模一样,那么它们再进行组合,所有字符肯定都出现偶数次)。
这样,通过折半搜索,时间复杂度就从O(2 ^ n)优化成O(2 ^ (n/2))了。

代码:

import java.util.*;

public class Main {
    static int[][] alphabet = new int[37][27];
    static long[] hash = new long[37];
    static long[] twoMul = new long[37];
    static HashMap<Long, Long> num = new HashMap<>();
    public static void main(String[] args) {
        int n, i, j, count;
        long loop, temp, k, ans = 0, hVal;
        twoMul[0] = 1;
        for (i = 1; i < 37; i++) {
            twoMul[i] = twoMul[i - 1] * 2;
        }
        StringBuilder stringBuilder = new StringBuilder();
        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt();
        for (i = 0; i < n; i++) {
            stringBuilder.delete(0, stringBuilder.length());
            stringBuilder.append(scanner.next());
            for (j = 0; j < stringBuilder.length(); j++) {
                if (alphabet[i][stringBuilder.charAt(j)-'a'] == 1) {
                    alphabet[i][stringBuilder.charAt(j)-'a'] = 0;
                } else {
                    alphabet[i][stringBuilder.charAt(j)-'a'] = 1;
                }
            }
        }
        for (i = 0; i < n; i++) {
            for (j = 0; j < 26; j++) {
                hash[i] += alphabet[i][j] * twoMul[j];
            }
        }
        for (loop = 1; loop <= twoMul[n / 2 + 1] - 1; loop++) {
            temp = loop;
            count = 0;
            k = 0;
            while (temp != 0) {
                if ((temp & 1) != 0) {
                    k = hash[count] ^ k;
                }
                temp >>= 1;
                count++;
            }
            if (k == 0 && count != 0) {
                ans++;
            }
            if (num.get(k) != null) {
                hVal = num.get(k);
                num.put(k, hVal + 1);
            } else {
                num.put(k, 1L);
            }
        }
        for (; loop <= twoMul[n] - 1; loop += twoMul[n / 2 + 1]) {
            temp = loop;
            count = n / 2 + 1;
            k = 0;
            while (temp >= twoMul[n / 2 + 1]) {
                if ((temp & twoMul[n / 2 + 1]) != 0) {
                    k = hash[count] ^ k;
                }
                temp >>= 1;
                count++;
            }
            if (k == 0 && count != n / 2 + 1) {
                ans++;
            }
            if (num.get(k) != null) {
                hVal = num.get(k);
                ans += hVal;
            }
        }
        System.out.println(ans);
    }


}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

phyit

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值