【LYZS2023暑假训练】 第三周题单总结与分析

原标题:2020寒假背包刷题总结

在这里插入图片描述

总结

1、不是所有的背包题都有这样一行或者类似这样一行状态转移方程

dp[j]=max(dp[j],???);

2、一定不要做单一来源和单一难度的题,不同题涉及“背包”的侧重不同,而且一定不要受模板限制。

:示例代码中read()为快读函数,write()为快写函数,均已省略

注意

  • 枚举重量时,如果开一维数组,01背包要倒序枚举,完全背包要正序枚举
  • 01背包开二维数组时可以正向枚举。

01背包,二维数组和一维数组的核心代码

//二维
 for (int i=1; i<=m; i++)
  for (int j=1; j<=t; j++) {//for(int j=v[i]
   f[i][j] = f[i-1][j];//  当前重量装不进,价值等于前i-1个物品
   if (j >= v[i])
    f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]); // 能装,需判断 是放这个物品还是不放价值大
  }
//一维
 for (int i=1; i<=m; i++)
  for (int j=t; j>=v[i]; j--) {
     f[j] = max(f[j],f[j-v[i]]+w[i]); // 能装,需判断 是放这个物品还是不放价值大
  }

第一类 裸背包题

1.P2639 [USACO09OCT]Bessie的体重问题Bessie’s Weight Problem G

USACO 2009
P2639 [USACO09OCT]Bessie的体重问题Bessie’s Weight Problem G
USACO 2009

//题目所给数据均在Int范围内
//每捆干草只能被吃一次(即使在列表中相同的重量可能出现2次,但是这表示的是两捆干草,其中每捆干草最多只能被吃掉一次)  
//这句告诉我们:这是一个01背包。
//当做价值和重量相等的01背包处理即可。
#define maxn 10005
int h,n,s[maxn],f[maxn],ans; 
int main(){
 h=read(),n=read();
 for(int i=1;i<=n;i++)
  s[i]=read();
 for(int i=1;i<=n;i++)
  for(int j=h;j>=s[i];j--){
   f[j]=max(f[j],f[j-s[i]]+s[i]);
  }
 for(int i=1;i<=h;i++)
  ans=max(ans,f[i]);
 write(ans);

第二类、稍有变形

1.P1164 小A点菜

P1164 小A点菜

#define maxn 10005
//每种菜品只有1份,要求正好花完m元,即一个"正好装满"的01背包。 
int dp[maxn][maxn],v[maxn],n,m; 
int main(){
 n=read(),m=read();
 for(int i=1;i<=n;i++)
  v[i]=read(); 
 for(int i=1;i<=n;i++)//开始选择菜品 
  for(int j=1;j<=m;j++){//正向枚举,枚举的是钱数 
   if(j<v[i])//买不起这道菜 
    dp[i][j]=dp[i-1][j];//实现当前钱数的点菜方案数和选到上一道菜时产生的方案数相同 
   if(j==v[i])//刚好买得起 
    dp[i][j]=dp[i-1][j]+1;//在经过上一道菜后产生的方案数上+1 
    //+1是因为其他组合可以达到j元,买这一道菜也刚好达到j元,买这一道菜算一个方案 
   if(j>v[i])//可以买完这道菜还有余额 
    dp[i][j]=dp[i-1][j]+dp[i-1][j-v[i]];//到上一道菜为止实现当前金额产生的方案数+
         //看过上一道菜后已经花费的钱同点了这道菜金额一样的方案数 
  }
  //一定要明确i和j的含义 
 write(dp[n][m]);
 return 0;
}

2.P1832 A+B Problem(再升级)

3.标题P2563 [AHOI2001]质数和分解

P1832 A+B Problem(再升级)

P2563 [AHOI2001] 质数和分解

两个题意是一模一样的,但是省选题(P2563)数据强大一点,反正我的质数和分解被卡了输出

//P2563 [AHOI2001]质数和分解
int s[201],f[201];
bool ps(int x){//筛法判断素数
    for(int i=2;i<=sqrt(x);i++)
        if(x%i==0) return 0;
    return 1;
}
int main(){
    int n;
    while(cin>>n){
        int ans=0;
        for(int i=2;i<=n;i++)//1不是质数不是合数 
            if(ps(i))
                s[++ans]=i;        
        memset(f,0,sizeof(f));
        f[0]=1;//重要但是易被忽略 
        for(int i=1;i<=ans;i++){
            for(int j=u[i];j<=200;j++)
                f[j]+=f[j-s[i]];//1个数拆成若干个素数和的方案数,
    //等同于拆成所有该数减去一个素数的差的方案数之和
        }
        cout<<f[n]<<endl;
    }
    return 0;
}

3.P1358扑克牌

P1358 扑克牌

//应用到乘法原理和组合数学
//边执行边取模能减小炸空间的可能,但是sum和dp[][]还是要开long long 
int n,m,x;
long long sum=1;
long long dp[maxn][maxn];//i张牌中已经取了j张 
#define mod 10007
int main(){
  n=read(),m=read();
 for(int i=0;i<=n;i++)
  dp[i][0]=1;
 for(int i=1;i<=min(n,m);i++)
  dp[i][i]=1;
 for(int i=1;i<=n;i++)//i张牌中已经取了j张 
  for(int j=1;j<=min(n,100);j++)//j上限的约束是因为每个人可能有0张可能有1张可能有多张牌
  //牌较少时每个人最多拿n张牌,牌足够多时每个人最多100张牌 
   dp[i][j]=(dp[i-1][j-1]+dp[i-1][j])%mod;//组合数性质
 for(int i=1;i<=m;i++){
  x=read();
  sum*=dp[n][x];
  sum=sum%mod;
  n-=x;
 }
 write(sum);
 return 0;
}

4.P1877 [HAOI2012]音量调节

P1877 [HAOI2012] 音量调节](https://www.luogu.com.cn/problem/P1877)

//题面是可以看到背包的影子的。
//这个题不需要"价值" 
//音量即“重量” 
//"调高调低"——“物品重量”为正和负 
#define MAXN 1005
using namespace std;
int n,begi,maxn,v[MAXN],f[MAXN][MAXN];
int main(){
 read(n),read(begi),read(maxn);
 f[0][begi]=1;//初始化,第1首歌开始需要的音量一定能实现。 
 for(int i=1;i<=n;i++)
  read(v[i]);
 for(int i=1;i<=n;i++)
  for(int j=begi;j<=maxn;j++){
   if(f[i-1][j])//选择调高还是调低 
    f[i][j+v[i]]=1,f[i][j-v[i]]=1;
 }
 for(int i=maxn;i>=0;i--)
  if(f[n][i]){//找到最后能达到的最大音量 
   write(i);
   return 0;
  }
 printf("-1");//不要学司延把else写在for外面的傻写法 
return 0;
}

5.P5662 [CSP-J2019] 纪念品

P5662 CSP-J2019 纪念品

int t,n,m,pre[maxn],pres[maxn],f[maxn],del[maxn],ans;
//核心思想:判断第二天商品是否涨价,并在第一天购买
//相当于多重背包,但开始手中的钱数只用于判断“是否买得起”
int main(){
 	f[0]=m;//f[0]可初始化为任何正数,保证第一天有购买东西的可能
	 t=read(),n=read(),m=read();
	 for(int i=1;i<=t;i++){
		  for(int j=1;j<=n;j++){
		  scanf("%d",&pre[j]);//当天的价格
		   pres[j]=del[j];//前一天的物品价格
		   del[j]=pre[j]-pres[j];//计算差价并视为物品价值
	  }
	  if(i>1){//至少从第二天开始“赚差价”
		  memset(f,0,sizeof(f)); //每天都只按照差价规划,与昨天的价格变动无关,所以每天从0开始
	  for(int b=1;b<=n;b++)//按物品遍历//c>=0&&
 		 for(int c=pres[b];c<=m;c++){//开始判断物品是否应该购买
 			  if(f[c-pres[b]]>=0)//如果物品在第二天涨价且手头有钱,就在第一天购买。
 	   f[c]=max(f[c],f[c-pres[b]]+del[b]);
   }
  	ans=0;
 	for(int i=0;i<=m;i++)
 		 ans=max(ans,f[i]);
			 m+=ans;
  }
 	for(int j=1;j<=n;j++)
	  del[j]=pre[j];//保留前一天的价格用以计算差价
 }
  write(m);
 return 0;
}

6.P2946 [USACO09MAR] Cow Frisbee Team S

P2946 USACO09MAR Cow Frisbee Team S

//没什么难度,就是特别注意dp开成一维数组会炸内存而且浪费
#define maxn 2005
const int mod=1e8;
int r[maxn];
int dp[maxn][maxn];
int n,f;
int main(){
	 dp[0][0]=1;
	 n=read(),f=read();
	 for(int i=1;i<=n;i++){
		  r[i]=read();
 	 for(int j=0;j<=f;j++){
 	  dp[i][j]=(dp[i][j]+dp[i-1][j])%mod;
  	 dp[i][j]=(dp[i][j]+dp[i-1][(j+r[i])%f])%mod;
  }
 }
 write(dp[n][f]);
 return 0;
}

第三类、穿上马甲别让做题的看出来我是个背包

1.P1044 栈

P1044 栈
(本次未收入题单中,该题有多种解法)

#define maxn 1005
int f[maxn][maxn],n;
//这道题的数据强度写不写记忆化搜索都可以 
inline int js(int x,int y){///x:操作队列里元素的个数,y:栈里的个数
 if(f[x][y])
  return f[x][y];
 if(x==0)
  return 1;//队列里没有元素了,方案不能再发生变化 
 if(y>0) //栈里还有元素 
  f[x][y]+=js(x,y-1);//达到当前状态的方案数加上栈里弹出一个元素状态的方案数 
 f[x][y]+=js(x-1,y+1);//一定要进行的操作就是当前状态方案数+
 //进栈一个(同时队列里少了一个元素)的方案数 
 return f[x][y];
}
int main(){
 n=read();
 write(js(n,0));
 return 0;
}

2.P5020 [NOIP2018 提高组] 货币系统

P5020 NOIP2018 提高组 货币系统

#define maxn 100005
int t,coin[maxn],m,n,mon[maxn];
//思维的重点不在背包,实现侧重背包一些 
//method 1,偏重数学思维 
//其实就是看已知硬币面额中,较大金额能否被多个较小金额凑出。 
int main(){
 t=read();
 while(t--){
  n=read();
  memset(coin,0,sizeof(coin));//多组数据注意每次都要初始化 
  memset(mon,0,sizeof(mon));
  m=0;
  for(int i=1;i<=n;i++)
   coin[i]=read(); 
  sort(coin+1,coin+1+n); //important
  for(int i=1;i<=n;i++)
   mon[coin[i]]=2;//用单枚原有硬币可以凑出的金额 
  for(int i=1;i<=coin[n];i++)//枚举金额 
  for(int j=1;j<=n;j++){//枚举硬币
   if(mon[i])//当前能凑出的面额,加一枚原有硬币也能凑出 
    if(i+coin[j]<=coin[n])//防止多余枚举 
     mon[i+coin[j]]=1; 
  }
   for(int j=1;j<=coin[n];j++)
    if(mon[j]==2)
     m++;
   write(m),printf("\n");
  }
 return 0;
} 
//method 2 背包 
#define maxn 1000005
int coin[maxn],mon[maxn];
int x,t,n,m;
int main(){
 t=read();
 while(t--){
 n=read();
 m=0;//运行多组数据的必要归零
 memset(mon,0,sizeof(mon));
 memset(coin,0,sizeof(coin));
 for(int i=1;i<=n;i++)
  coin[i]=read();
  sort(coin+1,coin+1+n);
 for(int i=1;i<=n;i++){//外层按货币枚举,能凑出来的面额置为1
  if(mon[coin[i]])//当前面额已经判断为1,进行下一重i循环
   continue;
  m++,mon[coin[i]]=1;//用单个已知货币能凑出的金额置1,且最小的几张一定要选
  for(int j=coin[i];j<=coin[n];j++)//内层按金额枚举
   if(mon[j-coin[i]])//不用这枚货币能凑出的金额,加上这枚货币可凑出一个新的金额
    mon[j]=1;
 }
 write(m),printf("\n");
 }
 return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

延7488

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值