传送门
题意:n个石头,每个石头有一个重量ai, 所有石头重量之和为S, 要求从中选一些石头,被选石头重量和为S’, 满足S’ >=S-S’ 且 从S’中去掉任何一个石头,上式不成立,也就是选的石头的重量和刚好>=总重量的一半。问共有多少种选择方案。
思路: 把序列通过贪心法进行预处理,即由大至小排序,然后设计状态dp[i][j]表示前i个石头里面选取若干个,使总和为j的方案个数。这样的状态转移方程dp[i][j] = dp[i-1][j] + dp[i-1][j-a[i]]。这种题型我是第一次做,因为不能单单的只是通过最终的结果来得出答案,因为你并不知道是否满足题目所给的条件,所以很巧妙的方法就是在过程中收集答案。对于一个状态dp[i][j]而言,为了不重复计算,我们只考虑一定选第i个的情况,这样如果j >= sum-j && j-a[i] <= sum-j, 则一定选i的方案数可以包含在答案中。
这种题目还是靠经验比较多,因为一般在设计状态的时候都是想怎么直接得出答案,但却可以设计一个较为简单的状态,然后得出答案
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<cstring>
using namespace std;
const long long MOD = 1e9+7;
long long dp[150000]; //dp[i][tot]表示考虑前i个石子,总数为tot,满足的条件数
long long a[305];
long long prefix[305];
bool cmp(long long a, long long b){
return a > b;
}
int main(){
int t;
cin >> t;
while(t--){
memset(dp, 0, sizeof(dp));
int n;
cin >> n;
long long sum = 0;
for(int i = 1; i <= n; ++i){
cin >> a[i];
sum += a[i];
}
sort(a+1, a+1+n, cmp);
long long ans = 0;
dp[0] = 1;
for(int i = 1; i <= n; ++i){
for(int tot = sum; tot >= a[i]; --tot){
dp[tot] = (dp[tot]+dp[tot-a[i]]) % MOD;
if(tot >= sum-tot && tot-a[i] <= sum-tot)
ans = (ans + dp[tot-a[i]]) % MOD;
}
}
cout << ans << endl;
}
return 0;
}