[题解] DAG上的 0/1 背包
[题解] DAG上的 0/1 背包
题目来源[2021-NOI教师培训T4]
题意描述
有一个 \(n\) 个点 \(m\) 条边的 \(DAG\),每个点都有一个权值和体积。
背包最大容量为 \(W\),要求从 \(1\) 号点走到 \(n\) 号点使得权值和最大,在此前提下满足体力值消耗最小。
体力值消耗量的定义为:
如果背包中的物品重量为 \(a\) ,行走距离为 \(b\) 时,花费的体力为 \(a\times b\)。
解题报告
考虑一般的 \(0/1\) 背包 \(f[i][j]\),\(i\) 这一维其实就是 \(DAG\) 上的点,不难想到 topo 排序来进行 DP 。
细节
如果出现了开始的入度为 \(0\) 的点,需要删掉,因为它们是废点(没有实际价值,还会导致 DP 出错)。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
template <typename T>
inline T read(){
T x=0;char ch=getchar();bool fl=false;
while(!isdigit(ch)){if(ch=='-')fl=true;ch=getchar();}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
}
return fl?-x:x;
}
#define LL long long
const int maxn = 2000 + 10 , maxm = 20000 + 10;
int f[maxn][maxn];
int g[maxn][maxn];
struct edge{
int to,nxt,w;
}e[maxm];
int head[maxn],cnt=0;
inline void link(int u,int v,int w){
e[++cnt].to=v;e[cnt].nxt=head[u];head[u]=cnt;e[cnt].w=w;
}
#include <queue>
int n,m,W;
int w[maxn],val[maxn],ru[maxn];
bool vis[maxn];
#define read() read<int>()
void topo(){
queue<int> q;
for(int i=2;i<=n;i++)if(!ru[i])q.push(i);
while(q.size()){
int u=q.front();q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
ru[v]--;
if(ru[v]==0 && v!=1)q.push(v);
}
}//除掉废点
while(!q.empty())q.pop();
q.push(1);
if(w[1]<=W)f[1][w[1]]=val[1];
while(!q.empty()){
int u=q.front();q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
for(int j=W;j>=0;j--){
if(j>=w[v]){
int value=f[u][j-w[v]]+val[v];
//cerr<<value<<endl;//
int cost=g[u][j-w[v]]+(j-w[v])*e[i].w;
if(f[v][j]<value || (value==f[v][j] && cost<g[v][j]))f[v][j]=value,g[v][j]=cost;
cost=g[u][j]+j*e[i].w;
if(f[v][j]<f[u][j] || (f[v][j]==f[u][j] && g[v][j]>cost))f[v][j]=f[u][j],g[v][j]=cost;
}
else{
int cost=g[u][j]+j*e[i].w;
if(f[v][j]<f[u][j] || (f[v][j]==f[u][j] && g[v][j]>cost))f[v][j]=f[u][j],g[v][j]=cost;
}
}
if(!--ru[v])q.push(v);
}
}
}
signed main(){
//freopen("1.in","r",stdin);
scanf("%d%d%d",&n,&m,&W);
for(int i=1;i<=n;i++)w[i]=read(),val[i]=read();
for(int i=1;i<=m;i++){
int u=read(),v=read(),w=read();link(u,v,w);ru[v]++;
}
topo();
int ans=0;int where=0;
for(int j=W;j>=0;j--){
if(f[n][j]>ans || (f[n][j]==ans && g[n][j]<where))ans=f[n][j],where=g[n][j];
}
printf("%d ",ans);
printf("%d\n",where);
return 0;
}