Problem 1608 - Calculation
Time Limit: 500MS Memory Limit: 65536KB Total Submit: 311 Accepted: 82 Special Judge: No
Description
Today, Alice got her math homework again!
She had n integers, and she needed to divide them into several piles or one pile. For each pile, if the teacher could get S, by + or – operator, then Alice got 1 small red flower. Alice wanted to get as many flowers as possible. Could you help her? Just tell her the maximum number of flowers she could get.
Input
The input consists of several test cases. The first line consists of one integer T (T <= 100), meaning the number of test cases. The first line of each test cases consists of two integer n (n<=14), meaning the number of the integer, and S (0<= S<= 100000000), meaning the result which teacher wanted. The next line consists of n integer a1, a2, …, an (0<= ai <= 10000000). You should know a few cases that n is larger than 12.
Output
For each test case, output one line with one integer without any space.
Sample Input
2
5 5
1 2 3 4 5
5 5
1 2 3 8 8
Sample Output
3 2
题解:
题目让求a集合的元素通过加减能组成S的最大组数,每个数字只能用一次;
看到这个题目就想着用dfs搜索,再状压下用的位置;但是华丽丽的wa了;首先自己的处理不对,时间复杂度是4^n,也就是2^28,超时不说,还有就是自己的太暴力了,并不一定是最优解;最后问了学长,学长一眼看出了我的错误,然后我的思路就行不通了。。。学长说这个应该是状压dp;
第一发状压dp,思想是dfs找解,如果找到dp状压的位置是1;然后for循环找到1~1<<n的所有的子集,dp[i]=max(子集的最优解+对i的补集的最优解,dp[i]);
代码:
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; int dp[1 << 14]; int a[14]; int n, S; void dfs(int i, int val, int cur){ if(i == n){ if(val == S){ dp[cur] = 1; } return; } dfs(i + 1, val, cur); dfs(i + 1, val + a[i], cur | (1 << i)); dfs(i + 1, val - a[i], cur | (1 << i)); } int main(){ int T; scanf("%d", &T); while(T--){ scanf("%d%d", &n, &S); for(int i = 0; i < n; i++){ scanf("%d", a + i); } fill(dp, dp + (1 << n), 0); dfs(0, 0, 0); for (int i = 1; i < (1 << n); i++){ for (int j = i & (i - 1); j > 0; j = i & (j - 1)){ dp[i] = max(dp[i], dp[j] + dp[j ^ i]); //j是i的子集,j ^ i 是i的补集; } } printf("%d ", dp[(1 << n) - 1]); } return 0; }
学长说还可以用中途对撞写,那样可以把dfs时间复杂度降到O(n*3^(n / 2)):
学长的中途对撞:
int d[1 << 14]; int a[14]; int n, s; void MakeSum(const vector<int> &x, vector< pair<int, int> > &res) { for (int i = 0; i < (1 << x.size()); ++i) { int cur = 0; for (int j = 0; j < x.size(); ++j) { if (i >> j & 1) cur -= x[j]; } res.push_back(make_pair(cur, i)); for (int j = i; j > 0; j = (j - 1) & i) { cur = 0; for (int k = 0; k < x.size(); ++k) { if (i >> k & 1) { if (j >> k & 1) cur += x[k]; else cur -= x[k]; } } res.push_back(make_pair(cur, i)); } } sort(res.begin(), res.end()); } void Init() { fill(d, d + (1 << n), 0); vector< pair<int, int> > left, right; int mid = n / 2; MakeSum(vector<int>(a, a + mid), left); MakeSum(vector<int>(a + mid, a + n), right); for (int i = 0; i < left.size(); ++i) { int lo = lower_bound(right.begin(), right.end(), make_pair(s - left[i].first, -1)) - right.begin(); int hi = upper_bound(right.begin(), right.end(), make_pair(s - left[i].first, 1 << 30)) - right.begin(); for (int j = lo; j < hi; ++j) { d[left[i].second | (right[j].second << mid)] = 1; } } } int main() { int T; scanf("%d", &T); while (T--) { scanf("%d%d", &n, &s); for (int i = 0; i < n; ++i) { scanf("%d", a + i); } Init(); for (int i = 1; i < (1 << n); ++i) { for (int j = (i - 1) & i; j > 0; j = (j - 1) & i) { d[i] = max(d[i], d[j] + d[i ^ j]); } } printf("%d ", d[(1 << n) - 1]); } return 0; }