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]); } }
浙公网安备 33010602011771号