POJ-4004:数字组合(用位移方法解组合数问题,Java版)

概述:

    看完题目大家应该就会想到这题可以用一个组合数的思路去编写代码。也就是说我们从给出的一个数组中去随机地抽取中若干个数相加,相加的和要等于给出的那个数。虽然大致思想是没错了,但是,具体的思路是怎么样的呢?


思路分析:

    我们是不是会有一种通用的方法来得到一维数组,用来告诉计算机,这个一维数组的某一个位置是我要选取的数呢?

可能大家都会想到的是递归,不过我一个朋友给了我另一个思路,那就是用位移来解决这个问题。为什么用位移,因为快!

    这里面有一个小技巧,那就是1<<n是等于2的n次方(例如1<<5 = 32)。也就是说把1左移n位,这里会得到一个结果,这个结果就是我们这个一维数组全部的子集,也就是全部的组合数。

    其实如果你了解数字在计算机中的存储和递增过程,那么你就会得到一个有趣的结论,那就是我从0(空集)到1<<n这些数代表的就是我们要告诉计算机在那些位上是我们需要留下的,那些位上的数是我们不需要的。关于数字在计算机中的存储和递增过程这里不作讲解。有兴趣的朋友可以去查阅相关资料。

    以上只是得到了全部的组合数。而他们也都是单个数字代表的,那我们能不能通过某种操作或是计算来获得一个标志数组或是某他的什么东西来说明,当前数组的第i位是需要的呢?因为单是一个数字代表的某种组合还不能让计算机完全理解,我们需要的是哪个位置的值。

下面我们假设有一个数字5,那我们应该怎么得到这个数字5所代表的组合呢?

0 1 0 1
0 0 0 1
上面的表格的第一行是数字5,第二行是数字1.

我们现在假设题目中告诉我们的是只有4个数。

那么,我们先得到a&1的值(&是位且操作符,它的作用是当两个数的每个二进制位的值都是1时,该位的值才是1,。。。。。。。想知道具体的,请查阅资料)。

这样,我们由第一个表格就可以得到一个数字1,我们把它保存在b[0]中。

这时,a右移a>>1 = 0010b

0 0 1 0
0 0 0 1
同理,这时b[1] = 0

a右移a>>1 = 0001

0 0 0 1
0 0 0 1
b[2] = 1

a右移a>>1 = 0000

0 0 0 0
0 0 0 1
b[3] = 0

a的位移结束。。。这时我们就得到了一个一维数组b[] = {1, 0, 1, 0};

我们可以看到它就是我们的a的二进制的逆序,如果你不喜欢逆序,那就用自己的方式获得正序的数组。


关于a的位移关键代码:

private static int[] getBitShift(int a, int n) {
		int[] bit = new int[n];
		for (int i = 0; i < n; ++i) {
			bit[i] = a&1;
			a >>= 1;
		}
		return bit;
	}


-------------------------------------------- 完整 AC CODE -----------------------------------------

import java.util.Scanner;

public class Main {

	private static final int N = 4;

	/**
	 * 通过位移得到一个组合数的一维数组
	 * @param a
	 * @param n
	 * @return
	 */
	private static int[] getBitShift(int a, int n) {
		int[] bit = new int[n];
		for (int i = 0; i < n; ++i) {
			bit[i] = a&1;
			a >>= 1;
		}
		return bit;
	}

	/**
	 * 得到某个bit数组所对应的sum值
	 * @param bit
	 * @param datas
	 * @return
	 */
	private static int getBitShiftSum(int[] bit, int[] datas) {
		int sum = 0;
		for (int i = 0; i < datas.length; i++) {
			if (bit[i] == 1) {
				sum += datas[i];
			}
		}
		return sum;
	}
	
	/**
	 * 得到整个数组中共有多少个这样的组合数
	 * @param datas
	 * @param t
	 * @return
	 */
	private static int getCount(int[] datas, int t) {
		int count = 0;
		for (int i = 0; i < (1 << datas.length); ++i) {
			int[] bit = getBitShift(i, datas.length);
			if (getBitShiftSum(bit, datas) == t) {
				count++;
			}
		}
		return count;
	}

	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		int n = input.nextInt();
		int t = input.nextInt();
		int[] datas = new int[n];
		for (int i = 0; i < n; i++) {
			datas[i] = input.nextInt();
		}
		System.out.println("" + getCount(datas, t));
	}
}


这里再给出我另一篇博客,《用递归和位移进行枚举子集合》这里是介绍了用递归和位移两种方法进行枚举子集合的过程,感兴趣的朋友可以去看看。。。

点击连接进行查看。

原文地址:https://www.cnblogs.com/fengju/p/6336154.html