解析:
本题目使用背包问题类型的DP算法,定义数组f[i][j]表示选取前i个元素其砝码质量和为j,通过f[i][j]=1来确定可以由
前i个砝码来凑出j.
其中DP的属性为判断f[i][j]的存在与否,集合划分分为不选择当前砝码,将砝码放在左盘(+),将砝码放在右盘(-)
代码:
#include<iostream>
using namespace std;
const int N=110;
const int M=2e5+10;//这里不可以为1e5+10,因为有f[i][sum+a[i]]
int f[N][M];
int a[N];
int main(){
int n;
cin>>n;
int sum=0;
for(int i=1;i<=n;i++){
cin>>a[i];
sum+=a[i];
}
f[0][0]=1;//这里一定要有!容易忽略,为什么要设f[0][0]=1呢?便于下边式子的计算
for(int i=1;i<=n;i++)
{
cout<<a[i]<<':'<<endl;
for(int j=0;j<=sum;j++){
f[i][j]=f[i-1][j]不选择此砝码+f[i-1][j+a[i]]放在右盘+f[i-1][abs(j-a[i])]放在左盘;
//对于最后一项要特别注意不可以写成以下形式:
/*
f[i][j]=f[i-1][j]+f[i-1][j+a[i]];
if(j>=a[i])
f[i][j]+=f[i-1][j-a[i]];
*/
//为什么不能写成这个样呢?
如果这样写表示的意思是前边砝码的质量和>0时才能将这个砝码放入左盘中,这显然是不对的,
正确的是如果该砝码放到左盘中这时砝码质量和>0就是合法的。也就是说其实砝码和质量的取值范围为-sum~sum
但是数字下标不能<0,所以如果想这样写数组下标必须都添加一个偏移量b.如代码2所示。
但是这里采用abs()即镜像,j=3,a[i]=4,a[i-1]=1,abs(j-a[i])=1,即如果j=3时,我也可以添加a[i]=4!!!
//这里会出现f[0][0]=1,f[1][0]=1,后者表示前1个砝码不做处理即不选择,这个不符合题意所以最后枚举的时候
//是从sum=1开始枚举的
cout<<f[i][j]<<' ';
}
cout<<endl;
}
int ans=0;
for(int i=1;i<=sum;i++){
if(f[n][i]) //为什么要枚举最后一个砝码的f[i][j]值呢,因为我们要选取n个砝码,每个砝码有3种选法,且最后的砝码重质量//在1~sum范围内
ans++;
}
cout<<ans<<endl;
return 0;
}
//代码2:
#include<iostream>
using namespace std;
const int N=110;
const int M=2e5+10;
int b=M/2;
int f[N][M];
int a[N];
int main(){
int n;
cin>>n;
int sum=0;
for(int i=1;i<=n;i++){
cin>>a[i];
sum+=a[i];
}
f[0][b]=1;
for(int i=1;i<=n;i++)
{
//cout<<a[i]<<':'<<endl;
for(int j=-sum;j<=sum;j++){
f[i][j+b]=f[i-1][j+b];
if(j-a[i]>=-sum)
f[i][j+b]|=f[i-1][j-a[i]+b];
if(j+a[i]<=sum)
f[i][j+b]|=f[i-1][j+a[i]+b]; //可以使用异或!!!因为这三种情况最多有一个1
//cout<<f[i][j]<<' ';
}
// cout<<endl;
}
int ans=0;
for(int i=1;i<=sum;i++){
if(f[n][i+b])
ans++;
}
cout<<ans<<endl;
return 0;
}