司延 2020寒假背包刷题总结

今天要给大年初一今天正月十三接近两个周的背包练习画一个句号。
采药写多了的人表示总结出来就一句话:不是所有的背包题都有这样一行或者类似这样一行状态转移方程

dp[j]=max(dp[j],balabala);//皮一下很开心

板子题就不放了
快读快输就不写了
引用题目均来自洛谷
注意:枚举重量时,如果开一维数组,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 We…
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点菜

#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(再升级)
P2563 [AHOI2001]质数和分解
两个题意是一模一样的,但是省选题数据强大一点,反正我的质数和分解被卡了输出

//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扑克牌

//应用到乘法原理和组合数学
//边执行边取模能减小炸空间的可能,但是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]音量调节

//题面是可以看到背包的影子的。
//这个题不需要"价值" 
//音量即“重量” 
//"调高调低"——“物品重量”为正和负 
#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-J 2019

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
USACO 2009

//没什么难度,就是特别注意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 栈
还有一种应用卡特兰数的写法

#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 提高组

#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;
}

总结起来就是em一定不要做单一来源和单一难度的题,不同题涉及“背包”的侧重不同,而且一定不要受模板限制。

你可能感兴趣的:(假期总结)