Calculation(dfs+状压dp)

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\n", 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\n", d[(1 << n) - 1]);
  }
  return 0;
}

 

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值