题目链接: https://nanti.jisuanke.com/t/41420
题意:
给你 n n n 个石子的集合 S S S (以下标为标识),并告诉你每个石子重量,现在要你选取一些石子 S ′ S' S′,要求满足以下条件。
①
s
u
m
(
S
′
)
>
s
u
m
(
S
−
S
′
)
sum(S')>sum(S-S')
sum(S′)>sum(S−S′)
② 对于任意一个在
S
′
S'
S′ 中的石子
i
i
i , 要求
s
u
m
(
S
′
)
−
w
e
i
g
h
t
[
i
]
<
=
s
u
m
(
S
−
S
′
)
sum(S')-weight[i]<=sum(S-S')
sum(S′)−weight[i]<=sum(S−S′)
请给出有多少种取法使得满足上述条件。
做法:
因为石子重量是小于等于 500 500 500 的,所以我们很容易想到枚举每个最小值,但是这样的话复杂度就会是 300 ∗ 500 ∗ 150000 ∗ 500 300*500*150000*500 300∗500∗150000∗500 ,绝对是超了的,所以有了这么个退背包的做法,一次次把物品从中拿出,并且去看合法的重量当前的方案数(这些方案如果包括这个值一定合法,因为在枚举的总是当前合法的最小的值)。
顺便贴一下这个东西的代码
01 01 01 背包的退背包做法
for(int i=a[k];i<=sum;i++)
dp[i]=dp[i]-dp[i-a[k]];
完全背包的退背包做法
for(int i=sum;i>=a[k];i--)
dp[i]=dp[i]-dp[i-a[k]];
代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(int)a;i<=(int)b;i++)
using namespace std;
typedef long long ll;
const int maxn=150005;
const int mod=1e9+7;
ll dp[maxn],a[305],ans,sum;
int main() {
int T,n; scanf("%d",&T);
while(T--){
int n; scanf("%d",&n); ans=0;
sum=0;
rep(i,1,n) scanf("%lld",&a[i]),sum+=a[i];
sort(a+1,a+1+n);
dp[0]=1;
for(int i=1;i<=n;i++){
for(int j=sum;j>=a[i];j--){
dp[j]=(dp[j]+dp[j-a[i]])%mod;
}
}
for(int i=1;i<=n;i++){
for(int j=a[i];j<=sum;j++) dp[j]=(dp[j]-dp[j-a[i]]+mod)%mod;
for(int j=0;j<=sum;j++){
if(j+a[i]>=sum-(j+a[i])&&j<=sum-j-a[i])
ans=(ans+dp[j])%mod;
}
}
printf("%lld\n",ans);
}
return 0;
}