【题目描述】
现在我们的手头有N个软件,对于一个软件i,它要占用Wi的磁盘空间,它的价值为Vi。我们希望从中选择一些软件安装到一台磁盘容量为M计算机上,使得这些软件的价值尽可能大(即Vi的和最大)。
但是现在有个问题:软件之间存在依赖关系,即软件i只有在安装了软件j(包括软件j的直接或间接依赖)的情况下才能正确工作(软件i依赖软件j)。幸运的是,一个软件最多依赖另外一个软件。如果一个软件不能正常工作,那么它能够发挥的作用为0。
我们现在知道了软件之间的依赖关系:软件i依赖软件Di。现在请你设计出一种方案,安装价值尽量大的软件。一个软件只能被安装一次,如果一个软件没有依赖则Di=0,这时只要这个软件安装了,它就能正常工作。
【输入格式】
第1行:N, M (0<=N<=100, 0<=M<=500)
第2行:W1, W2, … Wi, …, Wn (0<=Wi<=M )
第3行:V1, V2, …, Vi, …, Vn (0<=Vi<=1000 )
第4行:D1, D2, …, Di, …, Dn (0<=Di<=N, Di≠i )
【输出格式】
一个整数,代表最大价值。
【样例输入】
3 10
5 5 6
2 3 4
0 1 1
【样例输出】
5
【题意分析】
给你一堆软件,每个软件有重量和价值,而且有一个依赖对象,装了依赖对象才能装当前这个。
蒟蒻刚拿到这道题——这不裸的树上背包动规吗?于是暴力从没有依赖的软件(看做根节点)打了个树形DP,还过样例了。
于是自信满满地点了一下标签——缩点??!!
恍然大悟——哦!题目没说这是一棵树。。。
因此可能会存在多个环。我们想一下——一个环里的软件肯定是要么全装,要么全不装。那么我们就把一个环打包起来。。。成一个节点。剩下的就是一个DAG图(有向无环图),在这个DAG图上进行树形动规。
那么这些环将何去何从?把它们连到那里去?可以想一下,一旦撞到环了,肯定就不能往更深的地方下去了,因为它们忙着互相依赖,哪有功夫再往深的地方钻?那么我们就用简单暴力点的方法:弄一个虚拟节点0,把环和森林的根节点全都连到虚拟节点上面去。然后从虚拟节点开始树形DP。
树上背包问题:设
d
p
[
i
]
[
j
dp[i][j
dp[i][j]表示目前到i节点,花费j容量获得的最大价值。
那么
d p [ i ] [ j + w [ i ] ] = m a x ( d p [ i ] [ j + w [ i ] ] , d p [ i ] [ j + w [ i ] − k ] + d p [ v ] [ k ] dp[i][j+w[i]]=max(dp[i][j+w[i]],dp[i][j+w[i]-k]+dp[v][k] dp[i][j+w[i]]=max(dp[i][j+w[i]],dp[i][j+w[i]−k]+dp[v][k]
其中w[i]是当前这个节点的重量,v是枚举的每个子节点,k取遍0到j。
重新建图的时候原来的东西要清零~
Code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#define MAX 1000
using namespace std;
struct Front_Link_Star{
int next,to;
}edge[MAX];
stack <int> S;
int head[MAX],w[MAX],v[MAX],W[MAX],V[MAX],DAG[MAX];
int dfn[MAX],low[MAX],indegree[MAX],n,m,tot_circle,cnt,tag;
int dp[MAX][MAX];
bool vis[MAX],check[MAX][MAX];
inline void Add_Edge(int u,int v){
edge[++cnt].to=v;
edge[cnt].next=head[u];
head[u]=cnt;
}
inline int read(){
int s=0,w=1;
char ch=getchar();
while (!isdigit(ch)){if (ch=='-')w=-1;ch=getchar();}
while (isdigit(ch)){s=(s << 3)+(s << 1)+ch-'0';ch=getchar();}
return s*w;
}
inline void tarjan(int now){
dfn[now]=low[now]=++tag;
vis[now]=1;S.push(now);
for (register int i=head[now];i;i=edge[i].next){
int v=edge[i].to;
if (!dfn[v]){
tarjan(v);
low[now]=min(low[now],low[v]);
}else if (vis[v])low[now]=min(low[now],low[v]);
}
if (dfn[now]==low[now]){
++tot_circle;
int y;
do{
y=S.top();
DAG[y]=tot_circle;
vis[y]=0;
S.pop();
}while (now!=y);
}
} //缩点
inline void DP(int now){
for (register int i=W[now];i<=m;i++)
dp[now][i]=V[now]; //DP数组初始化
for (register int i=head[now];i;i=edge[i].next){
int v=edge[i].to;
DP(v); //先进儿子节点
for (register int j=m-W[now];j>=0;j--)
for (register int k=0;k<=j;k++)
dp[now][j+W[now]]=max(dp[now][j+W[now]],dp[now][j+W[now]-k]+dp[v][k]);
} //暴力DP
}
int main(){
n=read(),m=read();
for (register int i=1;i<=n;i++)
w[i]=read();
for (register int i=1;i<=n;i++)
v[i]=read();
for (register int i=1;i<=n;i++){
int x=read();
Add_Edge(x,i);
}
for (register int i=1;i<=n;i++)
if (!dfn[i])tarjan(i); //缩点
for (register int i=1;i<=n;i++){
W[DAG[i]]+=w[i],V[DAG[i]]+=v[i];
//节点合并
for (register int j=head[i];j;j=edge[j].next){
int v=edge[j].to;
if (DAG[i]!=DAG[v]){
++indegree[DAG[v]];
check[DAG[i]][DAG[v]]=true;
} //判断是否是环。
}
}
//重新建图。
//本来可以直接Add_Edge(i,j),因为会对图造成影响,所以弄一个判断数组。
cnt=0;
memset(edge,0,sizeof(edge));
memset(head,0,sizeof(head));
//清零别忘了。
for (register int i=1;i<=tot_circle;i++)
for (register int j=1;j<=tot_circle;j++)
if (check[i][j])Add_Edge(i,j);
for (register int i=1;i<=tot_circle;i++)
if (!indegree[i])Add_Edge(0,i);
//疯狂连边
DP(0); //从虚拟节点开始DP
printf("%d",dp[0][m]); //dp[0][m]为答案
return 0;
}