这道题是01背包的变形,唉好久没写dp背包都已经忘记了。传送门
dp[j]表示我们选出石墩重量为j的方案数。
这道题要求满足条件所有石头总重sum,对于挑选的重量sum'满足,在这对里面随便挑选一个石头重量为x,都能满足 sum' - x <= sum - sum' 并且还满足 sum' >= sum - sum'
那么我们可以用01背包的思想,我们枚举每一个sum'中最小的石头重量为x,那么对于一个sum到a[i]范围的sum'的重量为j, 那么我们就可以用 dp[j-a[i]]去更新当前的dp[j],同时我们还可以把合法sum'的值去更新我们的答案。
for(int i = n ; i >= 1 ; i--){
for(ll j = sum ; j >= a[i] ; j--){
if(2*j-a[i] <= sum && 2*j >= sum){
ans=(ans+dp[j-a[i]]) % MOD;
}
dp[j]=(dp[j]+dp[j-a[i]]) % MOD;
}
}
同时我们需要把a数组排一个序,因为我们此时能更新答案的sum'的值必须是由大于等于a[i]的值去构成的,因为我们已经规定a[i]是当前堆里面重量最小的,所以我们从大到小去构造,那么我们利用的答案都是合法的。
最后附上答案
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
const int MOD=1e9+7;
const int maxn = 2e5+10;
int dp[maxn],n,a[maxn];
inline void solve(){
ll sum=0,ans=0;
memset(dp,0,sizeof dp);dp[0]=1;
for(int i = 1; i <= n ; i++){
scanf("%d",&a[i]);
sum+=a[i];
}sort(a+1,a+n+1);
for(int i = n ; i >= 1 ; i--){
for(ll j = sum ; j >= a[i] ; j--){
if(2*j-a[i] <= sum && 2*j >= sum){
ans=(ans+dp[j-a[i]]) % MOD;
}
dp[j]=(dp[j]+dp[j-a[i]]) % MOD;
}
}printf("%lld\n",ans);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
int t;scanf("%d",&t);
while(t--){
scanf("%d",&n);
solve();
}
return 0;
}