1. 程式人生 > 實用技巧 >洛谷P1270-“訪問”美術館(樹上揹包問題)

洛谷P1270-“訪問”美術館(樹上揹包問題)

題目連結:https://www.luogu.com.cn/problem/P1270
CSDN食用連結:https://blog.csdn.net/qq_43906000/article/details/109231357

題目描述

經過數月的精心準備,Peer Brelstet,一個出了名的盜畫者,準備開始他的下一個行動。藝術館的結構,每條走廊要麼分叉為兩條走廊,要麼通向一個展覽室。Peer知道每個展室裡藏畫的數量,並且他精確測量了通過每條走廊的時間。由於經驗老到,他拿下一幅畫需要5秒的時間。你的任務是編一個程式,計算在警察趕來之前,他最多能偷到多少幅畫。

輸入格式

第1行是警察趕到的時間,以s為單位。第2行描述了藝術館的結構,是一串非負整數,成對地出現:每一對的第一個數是走過一條走廊的時間,第2個數是它末端的藏畫數量;如果第2個數是0,那麼說明這條走廊分叉為兩條另外的走廊。資料按照深度優先的次序給出,請看樣例。

一個展室最多有20幅畫。通過每個走廊的時間不超過20s。藝術館最多有100個展室。警察趕到的時間在10min以內。

輸出格式

輸出偷到的畫的數量

輸入輸出樣例
輸入
60
7 0 8 0 3 1 14 2 10 0 12 4 6 2
輸出
2

emmm,這題有兩個比較噁心的點,一個是輸入,一個就是題意。問在警察趕來之前,他最多能偷到多少幅畫,那麼隱含的一個條件就是他要返回出口的。。。。還有一個就是必須要嚴格小於,也就是在規定時間之前到達出口。

建圖的話我們可以借鑑一下線段樹動態開點的思想:

void build(int fa,int u)
{
	if (u==-1) return;
	if (!vis[u]) {u=++tot;vis[u]=1;}
	int s,nb;
	scanf ("%d%d",&s,&nb);
	val[u]=nb;
	add(fa,u,s); add(u,fa,s);
	if (nb) build(1,-1);
	else build(u,0),build(u,0);
}

接下來就是樹上操作了,由於他要求要返回,而且拿畫也需要花費時間,所以我們不能直接簡單粗暴地講時間除以2作為上限去找答案。我們只需要將路的長度乘以2就可以達到目的了。接下來就是初始化葉子節點了,我們只需要將時間除以5再取總的最小值就可以得到每個葉子在每個時間下能夠取得的最多畫了。然後接下來就是一個簡單的樹上01揹包問題了,設\(dp[i][j]\)表示第\(i\)個節點在時間為\(j\)的情況下最多能夠拿到的畫,那麼樹上的轉移方程也就很容易得到:\(dp[u][j]=max(dp[u][j],dp[v][k]+dp[u][j-k-eg[i].w])\)

以下是AC程式碼:

#include <bits/stdc++.h>
using namespace std;

const int mac=1e4+10;

struct node
{
	int to,next,w;
}eg[mac];
int tot=0,num=0,head[mac];
int val[mac],vis[mac];
int dp[mac][605],tims;

void add(int u,int v,int w)
{
	eg[num]=node{v,head[u],w*2};
	head[u]=num++;
}

void build(int fa,int u)
{
	if (u==-1) return;
	if (!vis[u]) {u=++tot;vis[u]=1;}
	int s,nb;
	scanf ("%d%d",&s,&nb);
	val[u]=nb;
	add(fa,u,s); add(u,fa,s);
	if (nb) build(1,-1);
	else build(u,0),build(u,0);
}

void dfs(int fa,int u)
{
	for (int i=head[u]; i!=-1; i=eg[i].next){
		int v=eg[i].to;
		if (v==fa) continue;
		dfs(u,v);
		for (int j=tims; j>=eg[i].w; j--){
			for (int k=0; k<=j-eg[i].w; k++)
				dp[u][j]=max(dp[u][j],dp[v][k]+dp[u][j-k-eg[i].w]);
		}
	}
}

int main(int argc, char const *argv[])
{
	scanf ("%d",&tims);
	tims--;
	tot=1;
	memset(head,-1,sizeof head);
	vis[1]=1;
	build(1,0);
	for (int i=1; i<=tot; i++) 
		if (val[i]){
			for (int j=0; j<=tims; j++)
				dp[i][j]=min(val[i],j/5);
		}
	dfs(-1,1); 
	printf("%d\n",dp[1][tims]);
	return 0;
}