洛谷P4241题解
我的第一篇紫题题解
思路
考虑到必须得要剩下一些毒瘤,且我们要考虑剩下毒瘤的最小值,我们就用 \(i\) 来表示剩下毒瘤的最小值,而小于它的体积此时必定需要放进去,所以说我们就用 \(sum\) 表示小于当前毒瘤价值的总体积数,而大于等于目前的剩下毒瘤体积最小值的毒瘤变需要进行一个分组背包统计答案,朴素的分组背包的复杂度是 \(O{(nmk)}\) 的,所以说必须进行优化。
优化
其他题解似乎讲的不太详细,我就给那些没有学过单调队列优化多重背包的人做一个粗略的解释。
\(dp[i][j]\) 表示装到第 \(i\) 个物品,占了 \(j\) 格空间的一个数,而我们注意到所有可以从 \(dp[i-1][k]\) 转移过来的状态必定满足
\[j\equiv k\pmod{K}
\]
在这里 \(K\) 表示第 \(i\) 个物品的价值。
所以我们可循环模拟 \(j\bmod{K}\) 的值,然后再模拟出 \(j\) 的值,此时的复杂度便减小到了 \(O{(nm)}\),而同时对于第 \(i\) 个物品,有数量的限制,一般情况下可以用单调队列作为滑动区间模拟数量的限制而统计最大值,而这道题统计方案数,所以说普通队列向右滑动,将队首依次踢掉即可。
实现
根据上文所述,我们可以表示出来递推公式:
\[\sum\limits_{i=m-sum-d+1}^{m-sum}dp_i
\]
依据题意,\(d\) 表示第 \(i\) 个物体的价值,\(dp_i\) 表示方案数量,\(m\) 表示背包空间,\(sum\) 如上文所表示的意思相同。
同时,我们对于如上的分组背包,要保证至少留一个毒瘤在外,所以我们要先进行数量减一的操作,最后在把答案统计完后要把一个毒瘤加上再进行分组背包。
本题仍然需要备份一个 \(dp\) 数组作为滚动数组的优化。
CODE
#include<stdio.h>
#include<algorithm>
#include<queue>
using namespace std;
const long long mod=19260817;
long long dp[2][100005];
long long ans;
struct node{
int k,d;
bool operator <(const node &p) const{//从大到小排列
return d>p.d;
}
};
int main(){
int n,m;
scanf("%d%d",&n,&m);
node input[605];
long long sum=0;
for(int i=1;i<=n;i++){
scanf("%d%d",&input[i].k,&input[i].d);
sum+=input[i].k*input[i].d;//求sum
}
if(sum<=m){//1种特判,如果所有毒瘤都可以被装下
printf("1");
return 0;
}
sort(input+1,input+n+1);
int q=0;
dp[0][0]=1;
for(int i=1;i<=n;i++){
sum-=input[i].k*input[i].d;
input[i].k --;//必须要剩下来一个,否则可能一个都不留
q^=1;
for(int j=0;j<input[i].d;j++){
long long cnt=0;
for(int k=0;k*input[i].d+j<=m;k++){//模拟队列求方案数
if(k>input[i].k){
cnt=(cnt+mod-dp[q^1][(k-input[i].k-1)*input[i].d+j])%mod;
}
dp[q][k*input[i].d+j]=(dp[q^1][k*input[i].d+j]+cnt)%mod;
cnt=dp[q][k*input[i].d+j];
}
}
for(int j=max(m-sum+1-input[i].d,(long long)0);j<=m-sum;j++){
ans+=dp[q][j];
ans%=mod;
//printf("%d ",ans);//ans求值
}
input[i].k++;
for(int j=0;j<input[i].d;j++){//重新进行分组背包
long long cnt=0;
for(int k=0;k*input[i].d+j<=m;k++){
if(k>input[i].k){
cnt=(cnt+mod-dp[q^1][(k-input[i].k-1)*input[i].d+j])%mod;
}
dp[q][k*input[i].d+j]=(dp[q^1][k*input[i].d+j]+cnt)%mod;
cnt=dp[q][k*input[i].d+j];
}
}
}
printf("%lld",ans);
return 0;
}
当然,您也可以选择用 STL 使代码进一步直观化
#include<stdio.h>
#include<algorithm>
#include<queue>
using namespace std;
const long long mod=19260817;
long long dp[2][100005];
long long ans;
struct node{
int k,d;
bool operator <(const node &p) const{
return d>p.d;
}
};
int main(){
int n,m;
scanf("%d%d",&n,&m);
node input[605];
long long sum=0;
for(int i=1;i<=n;i++){
scanf("%d%d",&input[i].k,&input[i].d);
sum+=input[i].k*input[i].d;
}
if(sum<=m){
printf("1");
return 0;
}
sort(input+1,input+n+1);
int q=0;
dp[0][0]=1;
for(int i=1;i<=n;i++){
sum-=input[i].k*input[i].d;
input[i].k --;
q^=1;
for(int j=0;j<input[i].d;j++){
long long cnt=0;
queue<int>f;
for(int k=0;k*input[i].d+j<=m;k++){
if(f.size()>input[i].k){
cnt=(cnt+mod-f.front())%mod;
f.pop();
}
dp[q][k*input[i].d+j]=(dp[q^1][k*input[i].d+j]+cnt)%mod;
cnt=dp[q][k*input[i].d+j];
f.push(dp[q^1][k*input[i].d+j]);
}
}
for(int j=max(m-sum+1-input[i].d,(long long)0);j<=m-sum;j++){
ans+=dp[q][j];
ans%=mod;
//printf("%d ",ans);
}
input[i].k++;
for(int j=0;j<input[i].d;j++){
long long cnt=0;
queue<int>f;
for(int k=0;k*input[i].d+j<=m;k++){
if(f.size()>input[i].k){
cnt=(cnt+mod-f.front())%mod;
f.pop();
}
dp[q][k*input[i].d+j]=(dp[q^1][k*input[i].d+j]+cnt)%mod;
cnt=dp[q][k*input[i].d+j];
f.push(dp[q^1][k*input[i].d+j]);
}
}
}
printf("%lld",ans);
return 0;
}
不使用队列会更快(那不是废话)。

浙公网安备 33010602011771号