源自vjudge上找到题目,都是背包DP的变式------(推荐点点前两个字🤓)
为什么先做背包而不是线性,因为我善
1.Cut Ribbon CF-189A
完全背包问题:
题意转换,有一个大小为 \(n\) 的背包,有三个物品,体积为 a,b,c,最多能装多少个物品。
但是这还不够,题目要求我们每段彩带的长度必须为 \(a,b,c\) 其中之一,换而言之,我们必须装满背包。
然后每个 \(f\) 赋初值唯一,特别的 \(f_0\) 初值为 0 ,因为长度为零的彩带只能分为 0 段,如果是 -1 的话是不是不太符合实际(我有-1条彩带?),再跑完全背包板子时要注意特判,如果 \(f_{i,j}\) 为 -1 就略过。
code:
memset(f,-1,sizeof(f));
f[0]=0;
for(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\) ,要求我们取出三个数 \(a_i,a_j,a_k\) \((1\le i\le j\le k\le n)\) 求 \(p*a_i+q*a_j+r*a_k\) 的最大值,并且会存在负数。
如何找状态转移?直接选三个数看起来很困难,可以考虑分步 DP ,我们把上面的式子从加号处分开,依次算每一项的解,这样会简单些。
所以有状态表示:\(f_{i,1/2/3}\)
- \(f_{i,1}\) 前 \(i\) 个数中 \(p*a_i\) 的最大值。
- \(f_{i,2}\) 前 \(i\) 个数中 \(p*a_i+q*a_j\) 的最大值。
- \(f_{i,3}\) 前 \(i\) 个数中 \(p*a_i+q*a_j+r*a_k\) 的最大值。
由此推出状态转移方程:
- \(f_{i,1}=max(f_{i-1,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)\)
将它们写在一起。
正确性:我们枚举到 \(a_x\) 时,有两种情况:选或不选。如果不选,对结果没有影响,如果选,则必定满足 \(f\) 的定义。
最后再注意一下初始化,当 \(f_{i,1}=f_{i,2}=f_{i,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背包 (哇,真的是你啊,哈哈):
先看题目:(以下求和函数\(\sum_{i=1}^{m}\) 简写成 \(\sum\) 因为我懒)
给出我们长度为 n 的序列 a,b ,选则多个 {\(a_i\),\(b_i\)} 使得 \(\frac { \sum a_i }{ \sum b_i}=k\) ,并且 \(\sum a_i\) 的值最大。
我们将公式转化一下,可得:\(\sum(a_i-b_i*k)=0\) 。
这样,我们只要让第 \(i\) 个水果的价值为 \(a_i-b_i*k\) ,最后只要让价值总和为 0 就可以了。
接下来的状态转移和表示就和 01背包 差不多了。
但是看到数据范围会发现数据会有负值,而数组下标不能是复制,所以我们计算一下极值,经计算,发现它的范围在 \([-10^5,10^4]\) ,所以只需要每个数加上 \(10^5\) 就可以了。
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];elsef[i][j]=max(f[i-1][j],f[i-1][j-m[i]]+a[i]);}}
}
4.The Great Mixing CF-788C
最伟大的混合 事 DP 罢:
(口瓜,不要把好几种可乐混一起口牙)
给出序列 a,找到若干个 \(\frac{a_i}{1000}\) ,使它们的和为 \(\frac{n}{1000}\)。并且我们可以重复选取同一个 \(a_i\)。
转化一下就是选若干个 \(a_i\) ,使他们的平均数为 n。
唉,🤓👆,还可以优化一下,把每个 \(a_i\) 减去 n,这样只要让他们的和为 0 就可以了。
我们又发现,\(k\le 10^6\) 不太好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 ,将已有浓度的可乐的权值的设为 \(w_i=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 序列,其中有 \(n_1\) 个 0 和 \(n_2\) 个 1,而且连续的 0 的个数不超过 \(k_1\) ,连续的 1 的个数不超过 \(k_2\) 个,这样的序列为合法序列。求合法序列方案数并且对 \(10^8\) 取模。
状态表示:\(f_{i,j,k,0/1}\) 表示已经使用了 \(i\) 个 1, \(j\) 个 0,并且有 \(k\) 个连续的 0/1,的方案数;(虽然是四维的但是好理解)
状态转移:
\((i>0,k\in[2,k_1])\) \(f_{i,j,k,0}=f_{i-1,j,k-1,0}\)
\((i>0,k\in[1,k_2])\) \(f_{i,j,1,0}+=f_{i-1,j,k,0}\)
\((j>0,k\in[2,k_2])\) \(f_{i,j,k,1}=f_{i,j-1,k-1,1}\)
\((j>0,k\in[1,k_1])\) \(f_{i,j,1,1}+=f_{i,j-1,k,1}\)
初始化 \(f_{1,0,1,0}=f_{0,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 棵树,每棵树上有 \(c_i\) 只鸟,可以消耗 \(cost_i\) 的魔法召唤一只鸟,并增加魔法上限 \(B\) , 她可以从 \(0~c_i\) 中召唤任意个数的鸟,她的初始魔法上限是 \(W\),当她从第 \(i\) 棵树走到第 \(i+1\) 棵树,并恢复 \(X\) 点魔法,并且不能超上限,求召唤的最多的鸟的数目。
首先看数据范围,发现体积的范围为 \([0~10^9]\) 所以不能用往常的方式做状态表示,既然魔法值和上线无法用来维护 DP,那么我们可以考虑其他的值,再观察数据范围,我们发现,\(\sum_{i=1}^{n} c_i\le 10^4\) 这提醒我们可以用鸟的数目来做状态表示。
状态表示:\(f_{i,j}\) 小恶魔走到第 \(i\) 棵树,召唤了 \(j\) 只鸟的魔法上限
状态转移:\(f_{i,j}=max\{ f_{i-1,j-k}+X-cost_i*k \}\)
因为存在魔法上限和魔法下限,我们还需要进行特判 \(0<f_{i,j}<W+B*j\)
当我们枚举 \(j\) 时要使用前缀和,因为随着走过的树的数目增加,\(j\) 的最大值是递增的,当枚举 \(k\) 时,我们要对枚举的 \(j\) 和 \(c_i\) 取最小值,因为在第 \(i\) 棵树上最多会有 \(c_i\) 只鸟。
当小恶魔没有走到任何一棵树时,她不会召唤鸟,所以她的魔法为 W,即 \(f_{0,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");