2017.10.27
今天主要復習背包內容了,比較基礎的dp,好久不打仍然會手生。其實復習考了這麽多次,發現我們其實考試的大部分內容都是學過的,但是經常會因為不常打造成“這個我學過,但寫不出來”的尷尬局面。所以經常復習,刷刷版子還是很重要的。
還有幾道水的就不寫了,放幾道今天稍微要想一下的
1.金明的預算方案
題目描述
金明今天很開心,家裏購置的新房就要領鑰匙了,新房裏有一間金明自己專用的很寬敞的房間。更讓他高興的是,媽媽昨天對他說:“你的房間需要購買哪些物品,怎麽布置,你說了算,只要不超過N元錢就行”。今天一早,金明就開始做預算了,他把想買的物品分為兩類:主件與附件,附件是從屬於某個主件的,下表就是一些主件與附件的例子:
主件 附件
電腦 打印機,掃描儀
書櫃 圖書
書桌 臺燈,文具
工作椅 無
如果要買歸類為附件的物品,必須先買該附件所屬的主件。每個主件可以有0個、1個或2個附件。附件不再有從屬於自己的附件。金明想買的東西很多,肯定會超過媽媽限定的N元。於是,他把每件物品規定了一個重要度,分為5等:用整數1~5表示,第5等最重要。他還從因特網上查到了每件物品的價格(都是10元的整數倍)。他希望在不超過N元(可以等於N元)的前提下,使每件物品的價格與重要度的乘積的總和最大。
設第j件物品的價格為v[j],重要度為w[j],共選中了k件物品,編號依次為j1,j2,……,jk,則所求的總和為:
v[j1]*w[j1]+v[j2]*w[j2]+ …+v[jk]*w[jk]。(其中*為乘號)
請你幫助金明設計一個滿足要求的購物單。
輸入輸出格式
輸入格式:
輸入的第1行,為兩個正整數,用一個空格隔開:
N m (其中N(<32000)表示總錢數,m(<60)為希望購買物品的個數。)
從第2行到第m+1行,第j行給出了編號為j-1的物品的基本數據,每行有3個非負整數
v p q (其中v表示該物品的價格(v<10000),p表示該物品的重要度(1~5),q表示該物品是主件還是附件。如果q=0,表示該物品為主件,如果q>0,表示該物品為附件,q是所屬主件的編號)
輸出格式:
輸出只有一個正整數,為不超過總錢數的物品的價格與重要度乘積的總和的最大值(<200000)。
輸入輸出樣例
輸入樣例#1:1000 5 800 2 0 400 5 1 300 5 1 400 3 0 500 2 0輸出樣例#1:
2200
題解:對於每一個主件,把它以及它搭配各附件的情況分別列舉,轉換成分組背包。
因為價格是10的倍數,除10後處理,就會優化時空復雜度。
#include<cstdio> #include<cstring> #include<algorithm> #include<vector> using namespace std; int n,m,v,p,q,dp[30010],ans,k,mp[30010]; struct node{ int v,w; node(int x,int y):v(x),w(y){} node(){} }; vector<node> a[65]; int main(){ scanf("%d%d",&n,&m); n/=10; for(int i=1;i<=m;i++){ scanf("%d%d%d",&v,&p,&q); v/=10; if(q==0){ a[++k].push_back(node(v,v*p)); mp[i]=k; } else{ int len=a[mp[q]].size(); for(int j=0;j<len;j++){ a[mp[q]].push_back(node(v+a[mp[q]][j].v,a[mp[q]][j].w+v*p)); } } } for(int i=1;i<=k;i++){ for(int j=n;j>=0;j--) for(int k=0;k<(a[i].size());k++){ if(j>=a[i][k].v)dp[j]=max(dp[j],dp[j-a[i][k].v]+a[i][k].w); } } printf("%d\n",dp[n]*10); return 0; }
2.瘋狂的采藥
題目背景
此題為NOIP2005普及組第三題的瘋狂版。
此題為紀念LiYuxiang而生。
題目描述
LiYuxiang是個天資聰穎的孩子,他的夢想是成為世界上最偉大的醫師。為此,他想拜附近最有威望的醫師為師。醫師為了判斷他的資質,給他出了一個難題。醫師把他帶到一個到處都是草藥的山洞裏對他說:“孩子,這個山洞裏有一些不同種類的草藥,采每一種都需要一些時間,每一種也有它自身的價值。我會給你一段時間,在這段時間裏,你可以采到一些草藥。如果你是一個聰明的孩子,你應該可以讓采到的草藥的總價值最大。”
如果你是LiYuxiang,你能完成這個任務嗎?
此題和原題的不同點:
1.每種草藥可以無限制地瘋狂采摘。
2.藥的種類眼花繚亂,采藥時間好長好長啊!師傅等得菊花都謝了!
輸入輸出格式
輸入格式:
輸入第一行有兩個整數T(1 <= T <= 100000)和M(1 <= M <= 10000),用一個空格隔開,T代表總共能夠用來采藥的時間,M代表山洞裏的草藥的數目。接下來的M行每行包括兩個在1到10000之間(包括1和10000)的整數,分別表示采摘某種草藥的時間和這種草藥的價值。
輸出格式:
輸出一行,這一行只包含一個整數,表示在規定的時間內,可以采到的草藥的最大總價值。
輸入輸出樣例
輸入樣例#1:70 3 71 100 69 1 1 2輸出樣例#1:
140
說明
對於30%的數據,M <= 1000;
對於全部的數據,M <= 10000,且M*T<10000000(別數了,7個0)。
題解:完全背包,挺裸的。就是把01背包正過來做。
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<algorithm> #define maxn 10000010 using namespace std; int t[maxn],v[maxn],f[maxn]; int main() { int m,n; scanf("%d%d",&m,&n); for(int i=1;i<=n;i++) scanf("%d%d",&t[i],&v[i]); for(int i=1;i<=n;i++) for(int j=t[i];j<=m;j++) f[j]=max(f[j],f[j-t[i]]+v[i]); printf("%d\n",f[m]); return 0; }
3.二叉蘋果樹
題目描述
有一棵蘋果樹,如果樹枝有分叉,一定是分2叉(就是說沒有只有1個兒子的結點)
這棵樹共有N個結點(葉子點或者樹枝分叉點),編號為1-N,樹根編號一定是1。
我們用一根樹枝兩端連接的結點的編號來描述一根樹枝的位置。下面是一顆有4個樹枝的樹
2 5 \ / 3 4 \ / 1 現在這顆樹枝條太多了,需要剪枝。但是一些樹枝上長有蘋果。
給定需要保留的樹枝數量,求出最多能留住多少蘋果。
輸入輸出格式
輸入格式:
第1行2個數,N和Q(1<=Q<= N,1<N<=100)。
N表示樹的結點數,Q表示要保留的樹枝數量。接下來N-1行描述樹枝的信息。
每行3個整數,前兩個是它連接的結點的編號。第3個數是這根樹枝上蘋果的數量。
每根樹枝上的蘋果不超過30000個。
輸出格式:
一個數,最多能留住的蘋果的數量。
輸入輸出樣例
輸入樣例#1:5 2 1 3 1 1 4 10 2 3 20 3 5 20輸出樣例#1:
21
題解:樹形dp,算比較難的一類dp吧,不過這道題還不是很難。
dp[i][j]表示以i為根保留j條的最優解。要先建樹,然後dfs。
#include <cstdio> #include <algorithm> #include <cstring> using namespace std; int dp[2010][2010],w,head[2010],fa[2010],tot,n,tt,sum,cnt,q,u,v; struct edge{ int next,v,w; }E[2010]; void add(int u,int v,int w){ E[++cnt].v=v; E[cnt].w=w; E[cnt].next=head[u]; head[u]=cnt; } int dfs(int x){ int ss=0; for(int i=head[x];i;i=E[i].next){ int v=E[i].v,w=E[i].w; if(v==fa[x])continue; fa[v]=x; ss+=dfs(v)+1; for(int j=min(q,ss);j>=1;j--) for(int k=min(q,j);k>=1;k--) dp[x][j]=max(dp[x][j],dp[x][j-k]+dp[v][k-1]+w); } return ss; } int main(){ scanf("%d%d",&n,&q); for(int i=1;i<n;i++){ scanf("%d%d%d",&u,&v,&w); add(u,v,w);add(v,u,w); } dfs(1); printf("%d",dp[1][q]); return 0; }
2017.10.27