无重复子集问题
Description
Mike is a lawyer with the gift of photographic memory. He is so good with it that he can tell you all the numbers on a sheet of paper by having a look at it without any mistake. Mike is also brilliant with subsets so he thought of giving a challange based on his skill and knowledge to Rachael. Mike knows how many subset are possible in an array of N integers. The subsets may or may not have the different sum. The challenge is to find the maximum sum produced by any subset under the condition:
The elements present in the subset should not have any digit in common.
Note: Subset {12, 36, 45} does not have any digit in common and Subset {12, 22, 35} have digits in common.Rachael find it difficult to win the challenge and is asking your help. Can youhelp her out in winning this challenge?
Input
First Line of the input consist of an integer T denoting the number of test cases. Then T test cases follow. Each test case consist of a numbe N denoting the length of the array. Second line of each test case consist of N space separated integers denoting the array elements.
Constraints:
1 <= T <= 100
1 <= N <= 100
1 <= array elements <= 100000
Output
Corresponding to each test case, print output in the new line.
Sample Input 1
1 3 12 22 35
Sample Output 1
57
解题思路:
dfs:
遍历所有可能的状态,找到最大的子数组和。因为不能有单个数字的重复,所以可以保存当前已经有的数字。对于每个元素来说,都有拿与不拿两种状态。
1.如果拿,那就更新当前已经有的数字状态,并进入下一个元素的判断;
2.如果不拿,就直接进入下一个元素的判断。
最终的答案一定在这两种情况之一,找更大值即可。
有了思路,可以用深度优先的方法。算法步骤如下:
1.定义一个状态数组记录当前已经包含数字,初始都为false。判断第一个元素
2.如果该元素的所有数字都不在状态数组中,则
一、将该元素数字加入状态数组,并带上已有的子数组和进入下一个元素至步骤2。
二、从状态数组中去除该元素数字(回溯),并带上已有的子数组和进入下一个元素至步骤2。
三、判断2.1和2.2步骤获得的值哪个大,返回更大的值
如果该元素的数字至少一个在状态数组中,进入下一个元素,重复步骤2。
dp:
但是用这种深度优先遍历的方法会有很多重复的计算,在n值较大时递归次数太多。所以可以用数组缓存已经计算得到的数据,我们已知的状态时当前计算的元素索引位置,以及当前包含数字的状态。可以定义数组dp[n][1024],dp[i][j]表示从第0-第i个元素数字包含状态为j时(将j转为二进制串即为0-9的包含状态),其最大子数组和。对于第i个元素,状态j:
如果状态j可以覆盖第i个元素所有数字,那么第i个元素可以拿,也可以不拿。dp[i][j] = Math.max(dp[i][j-第i个元素各个数字位] +arr[i], dp[i-1][j])。
如果状态j不能覆盖第i个元素所有数字,那么第i个元素肯定不能拿。dp[i][j] = dp[i-1][j];
得到动态规划转移方程后,可以使用递推的方法一直计算到dp[n-1][1024],然后在dp[n-1]中找到最大值即可。
完整代码如下:
1 import java.util.*; 2 3 public class Main { // 注意类名必须为Main 4 public static void main(String[] args) { 5 Scanner scan = new Scanner(System.in); 6 int x = scan.nextInt(); 7 scan.nextLine(); 8 // x个测试样例 9 for (int k = 0; k < x; k++){ 10 int n = scan.nextInt(); 11 int[] arr = new int[n]; 12 for (int i = 0; i < n; i++) 13 arr[i] = scan.nextInt(); 14 // 用数组存储中间计算结果 15 int[][] dp = new int[n][1024]; 16 // 初始状态dp[0] 17 for (int j = 0; j < 1024; j++) { 18 if (contains(j, arr[0])) 19 dp[0][j] = arr[0]; 20 } 21 // 递推计算剩余值 22 for (int i = 1; i < n; i++) { 23 for (int j = 0; j < 1024; j++) { 24 if (contains(j, arr[i])) 25 dp[i][j] = Math.max(dp[i-1][j], arr[i]+dp[i-1][split(j,arr[i])]); 26 else 27 dp[i][j] = dp[i-1][j]; 28 } 29 } 30 // 找最大值 31 int max = 0; 32 for (int j = 0; j < 1024; j++) 33 max = Math.max(max, dp[n-1][j]); 34 // 输出结果 35 System.out.println(max); 36 } 37 } 38 39 // 判断value2中所有数字是否都在value1转成的二进制串中存在 40 public static boolean contains(int value1, int value2) { 41 String str1 = Integer.toBinaryString(value1); 42 String str2 = String.valueOf(value2); 43 // str1前端补0直至长度为10 44 StringBuilder sb = new StringBuilder(); 45 for (int i = str1.length(); i < 10; i++) 46 sb.append('0'); 47 sb.append(str1); 48 String temp = sb.toString(); 49 50 for (int i = 0; i < str2.length(); i++) { 51 if (temp.charAt(str2.charAt(i)-'0') == '0') 52 return false; 53 } 54 return true; 55 } 56 57 // 从value1中将value2中所含数字去除 58 public static int split(int value1, int value2) { 59 String str1 = Integer.toBinaryString(value1); 60 String str2 = String.valueOf(value2); 61 // str1前端补0直至长度为10 62 char[] chars = new char[10]; 63 int offset = 10-str1.length(); 64 for (int i = 0; i < offset; i++) 65 chars[i] = '0'; 66 for (int i = 0; i < str1.length(); i++) 67 chars[i+offset] = str1.charAt(i); 68 // 替换掉temp中对应位置为0 69 for (int i = 0; i < str2.length(); i++) { 70 chars[str2.charAt(i)-'0'] = '0'; 71 } 72 return Integer.parseInt(new String(chars), 2); 73 } 74 75 76 }