砝码称重(考虑下标越上界)

3417.砝码称重

在这里插入图片描述
在这里插入图片描述

最首先,天平用砝码称出来的质量就是通过天平两边砝码的质量差

dp

早先的思路与做法(比较乱,推导方向不一致,容易遗漏)

为了避免考虑不周导致遗漏,需要使得递推式保持dp[i][j]由i-1哪些状态推导而来
假设dp[i][j]dp[i-1][x]推到而来,根据不同途径,反推x的值
依次考虑由简单到复杂的情况,
1、不放 dp[i][j]|=dp[i-1][j]
2、放在重的一边,假设之前是dp[i-1][x],放在重的一边自然是x+a[i]=j
反推得知,x=j-a[i], dp[i][j]|=dp[i-1][j-a[i]]
3、放在轻的一边 假设之前是dp[i-1][x],即重的一边与轻的一边的差值为x
a[i]<x,那么重的一边还是更重,x-a[i]=j,x=a[i]+j;
a[i]>x,那么原来重的一边会变成轻的一边,a[i]-x=j,x=a[i]-j;
思路就没这么清晰过嘿嘿嘿

 dp[i][j]|=dp[i-1][j];
	        if(j>=a[i])dp[i][j]|=dp[i-1][j-a[i]];//放在重的一边,判断是为了防止下标越界
	        //放在轻的一边,判断是为了防止下标越界
	        if(j<=a[i])dp[i][j]|=dp[i-1][a[i]-j];
	        dp[i][j]|=dp[i-1][a[i]+j];

- 总重M数组下标得开两倍,不然就会出错,

在这里插入图片描述
弄清楚为什么了?谨记:考虑下标越界时不要仅仅考虑越下界,数组上界也要考虑
不过数组大小只开 1e5

 if(a[i]+j<=sum)dp[i][j]|=dp[i-1][a[i]+j];

想要不考虑上界,就把数组大小开到两倍

#include<bits/stdc++.h>
using namespace std;
#define int long long 
const int N=105;
const int M=2e5+5;
int a[N];
int dp[N][M];
int n;
signed main(){
	cin>>n;
	int sum=0;
	for(int i=1;i<=n;i++)cin>>a[i],sum+=a[i];
	dp[0][0]=1;//记得初始化
	for(int i=1;i<=n;i++){
	    for(int j=0;j<=sum;j++){
	        dp[i][j]|=dp[i-1][j];
	        if(j>=a[i])dp[i][j]|=dp[i-1][j-a[i]];//放在重的一边,判断是为了防止下标越界
	        //放在轻的一边,判断是为了防止下标越界
	        if(j<=a[i])dp[i][j]|=dp[i-1][a[i]-j];
	        dp[i][j]|=dp[i-1][a[i]+j];
	        
	        //以上几种情况可以概况总结为
	       // dp[i][j]=dp[i-1][j]|dp[i-1][a[i]+j]|dp[i-1][abs(a[i]-j)];
	    }
	}
	int res=0;
	//正整数重量,不要考虑0噢
	for(int i=1;i<=sum;i++)if(dp[n][i])res++;
	cout<<res;
    return 0;
}

暴力做法(记忆化搜索)

//每个砝码有三种情况,放右边+,放中间,放左边-;
//一共n个砝码,搜索遍历每一种情况;
//把搜到的情况记录下来
//用bool记录每一种重量,如果可以就是true
//由于存在重量存在负数,给每个重量记录时加上一个偏移量B;也可以用绝对值abs

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;
const int N = 110,M=200010,B=M/2;
//st数组用来储存遍历过的情况,如果搜过了这个点,就不继续dfs了,节省时间。
//记忆化搜索。
bool st[N][M];
bool f[M];
int n;
int a[N];
void dfs(int u,int sum)
{
    if(st[u][sum]) return ; //已经遍历过了,就直接return;
    st[u][sum]=true;//记录遍历的点

    f[sum]=true;
    if(u>n) return;

    dfs(u+1,sum+a[u]);//放右边+
    dfs(u+1,sum);//放中间
    dfs(u+1,abs(sum-a[u]));//放左边-
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    dfs(1,0);

    int ans=0;
    for(int i=1;i<=M;i++) if(f[i]) ans++; //这里枚举要从1开始,能不能从0开始;
    cout<<ans;
    return 0;
}

作者:XSS
链接:https://www.acwing.com/solution/content/108799/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

bitset做法

#include<bits/stdc++.h>
using namespace std;
const int N=1e2+7,M=1e5+7;
bitset<M> S;//压位高精
int s[N];
int main(){
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
        cin>>s[i];
    S[0]=1;
    for(int i=0;i<n;i++)
        S|=S<<s[i];//先对组成的和进行合并,在对差值进行合并
    for(int i=0;i<n;i++)
        S|=S>>s[i];
    cout<<S.count()-1;//减去0
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值