D127 同余最短路 SPFA 算法 P9140 [THUPC 2023 初赛] 背包

D127 同余最短路 SPFA 算法 P9140 [THUPC 2023 初赛] 背包_哔哩哔哩_bilibili

 

P9140 [THUPC 2023 初赛] 背包 - 洛谷          背包 DP - OI Wiki

完全背包,求恰好装满的最大价值。$1≤n≤50,1≤v_i​≤10^5,1≤c_i​≤10^6,1≤q≤10^5,10^{11}≤V≤10^{12}$。

思路

如果跑完全背包,$O(nV)$ 会爆

用多个物品的体积 $v_i$ 拼凑总体积 $V$,要求总价值最大,考虑同余最长路

先贪心找出性价比($\frac {c_i}{v_i}$)最高的那个物品,记住其体积 $v_1$ 和价值 $c_1$,其他物品对 $v_1$ 取余

用反悔贪心的思想,先取最多的价值 $\lfloor \frac {V}{v_1} \rfloor \times c_1$,对零头 $V\%v_1$ 的价值进行反悔贪心,例取 $V=10$

例1. 两个物品 $(3,10),(1,1)$,反悔 $\dbinom{9}{30}\xrightarrow[+1-0\times 10]{+1-0\times 3}\dbinom{10}{31}$,连边 $0\xrightarrow[1-0\times 10]{} 1$

例2. 两个物品 $(3,10),(7,22)$,反悔 $\dbinom{9}{30}\xrightarrow[+22-2\times 10]{+7-2\times 3}\dbinom{10}{32}$,连边 $0\xrightarrow[22-2\times 10]{} 1$

例3. 两个物品 $(3,10),(2,4)$,反悔 $\dbinom{9}{30}\xrightarrow[+4-0\times 10]{+2-0\times 3}\dbinom{11}{34}\xrightarrow[+4-1\times 10]{+2-1\times 3}\dbinom{10}{28}$,连边 $0\xrightarrow[4-0\times 10]{} 2\xrightarrow[4-1\times 10]{} 1$

可以看出,例2 的体积 "退订" 两个 3,换成一个 7,恰好装满,获得最大价值 32

归纳出连边的代价为 $c_j-\lfloor\frac{i+v_j}{v_1}\rfloor\times c_1,\;i\in [0,v_1-1]$,连边 $add(i,\;(i+v_j)\%v_1,\;c_j-\lfloor\frac{i+v_j}{v_1}\rfloor\times c_1)$

因为出现了“负边权”,所以用 SPFA 跑最长路,答案为 $\lfloor \frac {V}{v_1} \rfloor \times c_1+d[V\%v_1]$,其中 $d[V\%v_1]$ 表示零头贡献的价值

恰好装满问题 $d_i$ 的初值均为 $-\infty$,0 点的初值为 0,如果所有组合都不能恰好装满,那么无法到达 $V\%v_1$ 点

相关板子:

D122【模板】同余最短路 Dijkstra 算法 P3403 跳楼机 - 董晓 - 博客园

D03【模板】最短路 Bellman-Ford 算法 SPFA 算法 P3385 负环 - 董晓 - 博客园

 

// 同余最短路 SPFA 算法 O(nm)
#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int N=1e5+5,M=5e6+5;
ll idx,h[N],to[M],ww[M],ne[M];
void add(ll u,ll v,ll w){
  to[++idx]=v;ww[idx]=w;ne[idx]=h[u];h[u]=idx;
}
ll n,q,v1,c1,V,v[55],c[55],d[N];
bool vis[N];

void SPFA(){
  for(int i=0; i<=v1; i++) d[i]=-1e18; d[0]=0;
  queue<ll> q; q.push(0);
  while(!q.empty()){
    ll u=q.front(); q.pop(); vis[u]=false;
    for(int i=h[u]; i; i=ne[i]){
      ll v=to[i],w=ww[i];
      if(d[v]<d[u]+w){
        d[v]=d[u]+w;
        if(!vis[v]) q.push(v),vis[v]=true;
      }
    }
  }
}
int main(){
  scanf("%lld%lld",&n,&q); v1=1,c1=0;
  for(int i=1; i<=n; i++){
    scanf("%lld%lld",&v[i],&c[i]); //体积,价值
    if(c[i]*v1>v[i]*c1) v1=v[i],c1=c[i]; //找出性价比c/v最大的
  }
  for(int i=0; i<v1; i++)for(int j=1; j<=n; j++){
    add(i,(i+v[j])%v1,c[j]-(i+v[j])/v1*c1);
  } 
  SPFA();
  for(int i=1; i<=q; i++){
    scanf("%lld",&V);
    if(d[V%v1]==-1e18) puts("-1");
    else printf("%lld\n",V/v1*c1+d[V%v1]);
  }
}

 

posted @ 2026-04-07 18:02  董晓  阅读(96)  评论(0)    收藏  举报