HAOI2010軟件安裝(樹形背包)
阿新 • • 發佈:2018-08-30
show algorithm while sdn max getc mem 並且 新的
HAOI2010軟件安裝(樹形背包)
題意
有n個物品,每個物品最多會依賴一個物品,但一個物品可以依賴於一個不獨立(依賴於其它物品)的物品,且可能有多個物品依賴一個物品,並且依賴關系可能形成一個環。現給你V的資金,問如何分配資金,可以使你的得到的總價值最大,請求出這個總價值。
解法
我以前寫過對於普通依賴性背包的博客:noip2006金明的預算方案如果對依賴性背包不是很熟悉的同學可以先看一下這道題。
由於這道題的依賴關系可能形成環,所以我們先用tarjan縮一下點,然後依賴關系圖就變成了一個森林,這時候我們再將每一棵樹的根節點向0號結點連一條邊,表示他們依賴0號結點。這時候我們就得到了一顆依賴關系樹。
那麽現在的重點就在如何處理這顆依賴關系樹。因為樹上每一個節點的決策數都太大了,所以同樣的我們考慮求出每一個以i節點為根的子樹在任意權值下的最大價值,然後再在i節點利用01背包來合並,一直遞歸往上。最後的答案就是0號結點所有方案中最優的那一個。
ps:如果還是看不懂的話,可以參觀這裏和這裏我也是在那兒學的。
代碼
以下的代碼是 \(O(nv^2)\) 的:
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <algorithm> #include <cctype> #include <vector> #define INF 2139062143 #define MAX 0x7ffffffffffffff #define del(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; template<typename T> inline void read(T&x) { x=0;T k=1;char c=getchar(); while(!isdigit(c)){if(c==‘-‘)k=-1;c=getchar();} while(isdigit(c)){x=x*10+c-‘0‘;c=getchar();}x*=k; } const int maxn=2500+15; int w[maxn],cost[maxn]; int n,m,ecnt; struct Edge{ int u,v; Edge(int u,int v):u(u),v(v){} }; vector<Edge> edge; vector<int> G[maxn]; void add_edge(int u,int v) { edge.push_back(Edge(u,v)); ecnt=edge.size(); G[u].push_back(ecnt-1); } int dfn[maxn],low[maxn],sta[maxn],top,bl[maxn],cnt; bool ins[maxn]; void tarjian(int u) { ins[u]=1;sta[++top]=u; dfn[u]=low[u]=++cnt; for(int i=0;i<G[u].size();i++) { Edge e=edge[G[u][i]]; int v=e.v; if(!dfn[v]) { tarjian(v); low[u]=min(low[u],low[v]); } else if(ins[v]) low[u]=min(low[u],dfn[v]); } if(low[u]==dfn[u]) { int y; while((y=sta[top--])&&y) { bl[y]=u; ins[y]=0; if(u==y) break; w[u]+=w[y],cost[u]+=cost[y]; } } } int dp[maxn][maxn]; void dfs(int u) { dp[u][cost[u]]=w[u]; for(int i=0;i<G[u].size();i++) { Edge e=edge[G[u][i]]; int v=e.v; dfs(v); for(int i=m;i>=cost[u];i--) for(int j=0;j<=i-cost[u];j++) dp[u][i]=max(dp[u][i],dp[u][i-j]+dp[v][j]); } } bool noroot[maxn]; int main() { read(n),read(m); for(int i=1;i<=n;i++) read(cost[i]); for(int i=1;i<=n;i++) read(w[i]); for(int i=1;i<=n;i++) { int d;read(d); if(!d) continue; add_edge(d,i); } for(int i=1;i<=n;i++) if(!dfn[i]) tarjian(i); int k=edge.size(); for(int i=0;i<=n;i++) G[i].clear(); for(int i=0;i<k;i++) { Edge e=edge[i]; int f1=bl[e.u],f2=bl[e.v]; if(f1==f2) continue;// 新的兩個點可能在同一個強連通分量中!! add_edge(f1,f2);noroot[f2]=1; } for(int i=1;i<=n;i++) if((bl[i]==i)&&(!noroot[i])) add_edge(0,i); dfs(0); int ans=0; for(int i=0;i<=m;i++) ans=max(ans,dp[0][i]); printf("%d\n",ans); return 0; }
這下面的是參照徐持橫的算法做到的 \(O(nv)\) 的算法:
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <algorithm> #include <cctype> #include <vector> #define INF 2139062143 #define MAX 0x7ffffffffffffff #define del(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; template<typename T> inline void read(T&x) { x=0;T k=1;char c=getchar(); while(!isdigit(c)){if(c==‘-‘)k=-1;c=getchar();} while(isdigit(c)){x=x*10+c-‘0‘;c=getchar();}x*=k; } const int maxn=500+15; int w[maxn],cost[maxn]; int n,m,ecnt; struct Edge{ int u,v; Edge(int u,int v):u(u),v(v){} }; vector<Edge> edge; vector<int> G[maxn]; void add_edge(int u,int v) { edge.push_back(Edge(u,v)); ecnt=edge.size(); G[u].push_back(ecnt-1); } int dfn[maxn],low[maxn],sta[maxn],top,bl[maxn],cnt; bool ins[maxn]; void tarjian(int u) { ins[u]=1;sta[++top]=u; dfn[u]=low[u]=++cnt; for(int i=0;i<G[u].size();i++) { Edge e=edge[G[u][i]]; int v=e.v; if(!dfn[v]) { tarjian(v); low[u]=min(low[u],low[v]); } else if(ins[v]) low[u]=min(low[u],dfn[v]); } if(low[u]==dfn[u]) { int y; while((y=sta[top--])&&y) { bl[y]=u; ins[y]=0; if(u==y) break; w[u]+=w[y],cost[u]+=cost[y]; } } } int dp[maxn][maxn]; void dfs(int u,int M) { if(M<=0) return; for(int i=0;i<G[u].size();i++) { Edge e=edge[G[u][i]]; int v=e.v; for(int j=1;j<=M;j++) dp[v][j]=dp[u][j]; dfs(v,M-cost[v]); for(int j=cost[v];j<=M;j++) dp[u][j]=max(dp[u][j],dp[v][j-cost[v]]+w[v]); } } bool noroot[maxn]; int main() { read(n),read(m); for(int i=1;i<=n;i++) read(cost[i]); for(int i=1;i<=n;i++) read(w[i]); for(int i=1;i<=n;i++) { int d;read(d); if(!d) continue; add_edge(d,i); } for(int i=1;i<=n;i++) if(!dfn[i]) tarjian(i); int k=edge.size(); for(int i=0;i<=n;i++) G[i].clear(); for(int i=0;i<k;i++) { Edge e=edge[i]; int f1=bl[e.u],f2=bl[e.v]; if(f1==f2) continue;// 新的兩個點可能在同一個強連通分量中!! add_edge(f1,f2);noroot[f2]=1; } for(int i=1;i<=n;i++) if((bl[i]==i)&&(!noroot[i])) add_edge(0,i); dfs(0,m); int ans=0; for(int i=0;i<=m;i++) ans=max(ans,dp[0][i]); printf("%d",ans); return 0; }
HAOI2010軟件安裝(樹形背包)