题目链接:http://acm.whu.edu.cn/land/problem/detail?problem_id=1608
题意:将n个数划分为若干个部分,然后每一部分里面的数进行+/-操作,如果一个部分里面的数可以凑成s,那么就计1,问最多可以凑成多少个s。
比如1 2 3 4 5 -> (1+4) (2+3) (5) 1 2 3 8 8 -> (8-3) (8-1-2)
思路:状态压缩Dp,用二进制表状态先预处理状态为s时的和,然后再求每个状态能否构成s,最后再计算每个状态最多能构成多少个s,后两个可以借助位运算来枚举子集。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <stack>
#include <map>
#include <set>
#include <vector>
#include <sstream>
#include <queue>
#include <utility>
using namespace std;
#define rep(i,j,k) for (int i=j;i<=k;i++)
#define Rrep(i,j,k) for (int i=j;i>=k;i--)
#define Clean(x,y) memset(x,y,sizeof(x))
#define LL long long
#define ULL unsigned long long
#define inf 0x7fffffff
#define mod %100000007
int const maxn = 1<<14;
int T;
int n,s;
int a[15];
int sum[maxn];
int ans[maxn];
int f[maxn];
int main()
{
cin>>T;
while(T--)
{
Clean(f,0);
Clean(sum,0);
Clean(ans,0);
scanf("%d %d",&n,&s);
rep(i,1,n) scanf("%d",&a[i]);
int uplim = 1<<n;
for( int i = 1; i <= uplim - 1; i++ ) //先处理当前状态的和
for( int j = 0; j < n; j++ )
if ( i & 1<<(j) ) sum[i] += a[j+1];
for( int i = 1; i <= uplim-1; i++ )
{
ans[i] = ( sum[i] == s );
if ( !ans[i] ) //当前状态的数是否可以凑成s
{
for( int j = i & ( i - 1 ); j; j = i & ( j - 1 ) )
{
ans[i] = ans[i] | ( ( sum[j] == s + sum[i & ~j] ) || ( sum[i & ~j] == s + sum[i] ) );
}
}
f[i] = ans[i]; //当前状态的这些数最多可以凑成几个s
for( int j = i & ( i - 1 ); j; j = i & ( j - 1 ) )
{
f[i] = max( f[i] , f[j] + f[i & ~j] );
}
}
printf("%d\n",f[uplim-1]);
}
return 0;
}