[题解] DAG上的 0/1 背包

[题解] DAG上的 0/1 背包

题目来源[2021-NOI教师培训T4]

传送门(S2OJ)

题意描述

有一个 \(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;
}
posted @ 2021-08-12 17:31  ¶凉笙  阅读(79)  评论(0编辑  收藏  举报