题目模型
给出具有n个数字的集合,求最多能删除多少个数,使得删除后的集合和原集合等价,等价的定义为:对于任意非负整数,原集合能表示则新集合也能表示出。每个数字可以使用无数次。
思路
进一步分析,哪些数字是能删除的?
如果这个数字能够通过原集合中的其它数字组合出,则这个数字可以删除。
这其实是一个完全背包问题。
将集合内的数字升序排序,推一遍完全背包,在推的过程中,判断a[i]是否能被前i个数组合出来。(组合方式是求和,且已升序排序,推到这里组合不出来,后面也不会组合出来了)。
定义状态 d p [ i ] [ j ] dp[i][j] dp[i][j] 为从前 i i i个数字,背包容量为j的情况下,能组合出的最大和。
状态转移方程为: d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ j − a [ i ] ∗ k ] + k ∗ a [ i ] ) dp[i][j] = max(dp[i][j], dp[i-1][j-a[i]*k] + k*a[i]) dp[i][j]=max(dp[i][j],dp[i−1][j−a[i]∗k]+k∗a[i])
如果 d p [ i ] [ a [ i ] ] = d p [ i − 1 ] [ a [ i ] ] dp[i][a[i]]=dp[i-1][a[i]] dp[i][a[i]]=dp[i−1][a[i]],则说明前i-1个数字可以组合出 a [ i ] a[i] a[i]。
显然,状态转移方程可优化为:
d
p
[
j
]
=
m
a
x
(
d
p
[
j
]
,
d
p
[
j
−
a
[
i
]
]
+
a
[
i
]
)
dp[j] = max(dp[j], dp[j-a[i]] + a[i])
dp[j]=max(dp[j],dp[j−a[i]]+a[i]),
j
>
=
a
[
i
]
j>=a[i]
j>=a[i]
完全背包具体优化过程
特别地,当
j
=
a
[
i
]
j =a[i]
j=a[i]时,在转移之前,如果
d
p
[
j
]
=
j
dp[j]=j
dp[j]=j ,则
a
[
i
]
a[i]
a[i]可删。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100+10;
const int maxm = 25000+10;
int n,m, a[maxn];
int dp[maxm];
inline void init(){
memset(dp,0,sizeof(dp));
}
inline int cal(){
int ans = 0;
m = a[n];
for(int i = 1;i<=n;i++){
int j = a[i];
if(dp[j] != j){
dp[j] = j;
ans++;
}
for(j++;j<=m;j++){
dp[j] = max(dp[j-a[i]]+a[i],dp[j]);
}
}
return ans;
}
int main(){
int t;scanf("%d",&t);
while(t--){
scanf("%d",&n);
for(int i = 1;i<=n;i++){
scanf("%d",&a[i]);
}
sort(a+1,a+n+1);
init();
printf("%d\n",cal());
}
return 0;
}