CF的背包DP (备用笔记)

源自vjudge上找到题目,都是背包DP的变式------(推荐点点前两个字🤓)
为什么先做背包而不是线性,因为我善

1.Cut Ribbon CF-189A

完全背包问题:
题意转换,有一个大小为 n 的背包,有三个物品,体积为 a,b,c,最多能装多少个物品。

但是这还不够,题目要求我们每段彩带的长度必须为 a,b,c 其中之一,换而言之,我们必须装满背包。

然后每个 f 赋初值唯一,特别的 f0 初值为 0 ,因为长度为零的彩带只能分为 0 段,如果是 -1 的话是不是不太符合实际(我有-1条彩带?),再跑完全背包板子时要注意特判,如果 fi,j 为 -1 就略过。

code

memset(f,-1,sizeof(f));
f[0]=0for(int i=1;i<=3;i++){
    for(int j=v[i];j<=n;j++){
        if(f[j-v[i]]<0) continue;
        f[j]=max(f[j],f[j-v[i]]+1);
    }
}

2.Marvolo Gaunt's Ring CF-855B

应该事 01背包 罢:

简单理解一下题意:给出一个序列 a ,要求我们取出三个数 ai,aj,ak (1ijkn)pai+qaj+rak 的最大值,并且会存在负数。

如何找状态转移?直接选三个数看起来很困难,可以考虑分步 DP ,我们把上面的式子从加号处分开,依次算每一项的解,这样会简单些。

所以有状态表示:fi,1/2/3

  • fi,1i 个数中 pai 的最大值。
  • fi,2i 个数中 pai+qaj 的最大值。
  • fi,3i 个数中 pai+qaj+rak 的最大值。

由此推出状态转移方程:

  • fi,1=max(fi1,1,pai)
  • fi,2=max(fi1,2,fi,1+qai)
  • fi,3=max(fi1,3,fi,2+rai)

将它们写在一起。

正确性:我们枚举到 ax 时,有两种情况:选或不选。如果不选,对结果没有影响,如果选,则必定满足 f 的定义。

最后再注意一下初始化,当 fi,1=fi,2=fi,3=8e18 有负数存在,初始化的数尽量小些。别忘了开 long long,小心见祖宗
code:

f[0][1]=f[0][2]=f[0][3]=-8e18;
for(int i=1;i<=n;i++){
    f[i][1]=max(f[i][1],p*a[i]);
    f[i][2]=max(f[i-1][2],f[i][1]+q*a[i]);
    f[i][3]=max(f[i-1][3],f[i][2]+r*a[i]);
}

3.Dima and Salad CF-366C

也事 01背包 (哇,真的是你啊,哈哈):

先看题目:(以下求和函数i=1m 简写成 因为我懒)
给出我们长度为 n 的序列 a,b ,选则多个 {aibi} 使得 aibi=k ,并且 ai 的值最大。

我们将公式转化一下,可得:(aibik)=0
这样,我们只要让第 i 个水果的价值为 aibik ,最后只要让价值总和为 0 就可以了。

接下来的状态转移和表示就和 01背包 差不多了。
但是看到数据范围会发现数据会有负值,而数组下标不能是复制,所以我们计算一下极值,经计算,发现它的范围在 [105,104] ,所以只需要每个数加上 105 就可以了。

code:

for(int i=1;i<=n;i++){
    for(int j=110000;j>=0;j--){
        if(j-m[i]<=110000 && j-m[i]>=0){ 
           if(f[i-1][j-m[i]]==-1)
              f[i][j]=f[i-1][j];
           else
              f[i][j]=max(f[i-1][j],f[i-1][j-m[i]]+a[i]);
        }
    }
}

4.The Great Mixing CF-788C

最伟大的混合 事 DP 罢:

口瓜,不要把好几种可乐混一起口牙

给出序列 a,找到若干个 ai1000 ,使它们的和为 n1000。并且我们可以重复选取同一个 ai

转化一下就是选若干个 ai ,使他们的平均数为 n。
唉,🤓👆,还可以优化一下,把每个 ai 减去 n,这样只要让他们的和为 0 就可以了。

我们又发现,k106 不太好DP,唉,🤓👆我们发现除数是 1000,也就意味着本质不同的数一共就 1000 个(去重),上下界大概开 1e6就可以了(开大些,但不能完全开大

这种背包做法会 TLE 即使 CF 的神机有 1s 1e9的运算量

code

for(int i=1;i<=m;i++){
    f[N+a[i]]=1;
    if(a[i]<0){ 
        for(int j=sum;j>=-sum;j--){
            f[j+N]=min(f[j+N],f[j+N-a[i]]+1);
        }
    }
    else{
        for(int j=-sum;j<=sum;j++){
            f[j+N]=min(f[j+N],f[j+N-a[i]]+1);
        }
    }
}

正解( T 不了了😋):

可以用 BFS ,将已有浓度的可乐的权值的设为 wi=1,其它为inf,然后把它们放进队列。每次取队头元素,把它加上所有可能的饮料的情况都枚举一遍。若有新扩展的点,加入队列。

最后结果如果 w[1000] 没有被更新,则可以无法混合出来,否则可以混合出来。(因为我们存数组时加了 1000 ,所以此时的 w[1000] 是 w'[0])

code

//BFS过程,如何可以最好设 P=1000,Q=2000 万一少个或多个零就寄了。
for(int i=0;i<=2000;i++){
    if(w[i]==1) q.push(i);
}

while(!q.empty()){
    int u=q.front();
    q.pop();

    for(int i=0;i<=2000;i++){
        if(w[i]==1 && w[u+i-1000]==inf){
            q.push(u+i-1000);
            w[u+i-1000]=w[u]+1;
        }
    }
}

5.Caesar's Legions CF-118D

也事 背包DP(废话):

给出一个 01 序列,其中有 n1 个 0 和 n2 个 1,而且连续的 0 的个数不超过 k1 ,连续的 1 的个数不超过 k2 个,这样的序列为合法序列。求合法序列方案数并且对 108 取模。

状态表示:fi,j,k,0/1 表示已经使用了 i 个 1, j 个 0,并且有 k 个连续的 0/1,的方案数;(虽然是四维的但是好理解)

状态转移:

(i>0k[2,k1]) fi,j,k,0=fi1,j,k1,0
(i>0k[1,k2]) fi,j,1,0+=fi1,j,k,0
(j>0k[2,k2]) fi,j,k,1=fi,j1,k1,1
(j>0k[1,k1]) fi,j,1,1+=fi,j1,k,1

初始化 f1,0,1,0=f0,1,1,1=1 只使用 1 个 0/1 的方案数必定是 1。
计算过程与结果别忘了取模,血与泪的教训

code

f[1][0][1][0]=f[0][1][1][1]=1;
for(int i=0;i<=n1;i++){
    for(int j=0;j<=n2;j++){
        if(i>0){
            for(int k=2;k<=k1;k++){
                f[i][j][k][0]=f[i-1][j][k-1][0];
            }
            for(int k=1;k<=k2;k++){
                f[i][j][1][0]=(f[i][j][1][0]+f[i-1][j][k][0])%mod;
            }
        }
        if(j>0){
            for(int k=2;k<=k2;k++){
                f[i][j][k][1]=f[i][j-1][k-1][1];
            }
            for(int k=1;k<=k1;k++){
                f[i][j][1][1]=(f[i][j][k][1]+f[i][j-1][k][1])%mod;
            }
        }
    }
}

6.Birds CF-922E

啊?小恶魔? 嗯,背包:

有 n 棵树,每棵树上有 ci 只鸟,可以消耗 costi 的魔法召唤一只鸟,并增加魔法上限 B , 她可以从 0 ci 中召唤任意个数的鸟,她的初始魔法上限是 W,当她从第 i 棵树走到第 i+1 棵树,并恢复 X 点魔法,并且不能超上限,求召唤的最多的鸟的数目。

首先看数据范围,发现体积的范围为 [0 109] 所以不能用往常的方式做状态表示,既然魔法值和上线无法用来维护 DP,那么我们可以考虑其他的值,再观察数据范围,我们发现,i=1nci104 这提醒我们可以用鸟的数目来做状态表示。

状态表示:fi,j 小恶魔走到第 i 棵树,召唤了 j 只鸟的魔法上限

状态转移:fi,j=max{fi1,jk+Xcostik}
因为存在魔法上限和魔法下限,我们还需要进行特判 0<fi,j<W+Bj

当我们枚举 j 时要使用前缀和,因为随着走过的树的数目增加,j 的最大值是递增的,当枚举 k 时,我们要对枚举的 jci 取最小值,因为在第 i 棵树上最多会有 ci 只鸟。
当小恶魔没有走到任何一棵树时,她不会召唤鸟,所以她的魔法为 W,即 f0,0=W
最后倒序枚举,求出召唤出的鸟的最大数目就可以了。

code:

memset(f,-1,sizeof(f));
f[0][0]=W;
for(int i=1;i<=n;i++){
    for(int j=0;j<=s[i];j++){
        int m=min(c[i],j);
        for(int k=0;k<=m;k++){
            if(f[i-1][j-k]-v[i]*k>=0 && f[i-1][j-k]!=-1){
                f[i][j]=max(f[i][j],f[i-1][j-k]+X-v[i]*k);					
            }
        }
        if(f[i][j]>W+B*j) f[i][j]=W+B*j;
     }
}
for(int i=s[n];i>=1;i--){
    if(f[n][i]!=-1){
      printf("%lld\n",i);
      return 0;
    }
}
puts("0");
posted @   麻之妖精  阅读(40)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示