P2515 [HAOI2010]软件安装
题目描述
现在我们的手头有\(N\)个软件,对于一个软件\(i\),它要占用\(W_i\)的磁盘空间,它的价值为\(V_i\)。我们希望从中选择一些软件安装到一台磁盘容量为\(M\)计算机上,使得这些软件的价值尽可能大(即\(V_i\)的和最大)。
但是现在有个问题:软件之间存在依赖关系,即软件i只有在安装了软件\(j\)(包括软件\(j\)的直接或间接依赖)的情况下才能正确工作(软件\(i\)依赖软件\(j\))。幸运的是,一个软件最多依赖另外一个软件。如果一个软件不能正常工作,那么它能够发挥的作用为0。
我们现在知道了软件之间的依赖关系:软件\(i\)依赖软件\(D_i\)。现在请你设计出一种方案,安装价值尽量大的软件。一个软件只能被安装一次,如果一个软件没有依赖则\(D_i=0\),这时只要这个软件安装了,它就能正常工作。
输入输出格式
输入格式:
第1行:\(N,M\) \((0<=N<=100,0<=M<=500)\)
第2行:\(W_1,W_2,...W_i,...,W_n\) \((0<=W_i<=M)\)
第3行:\(V_1, V_2, ..., V_i, ..., V_n\) \((0<=V_i<=1000)\)
第4行:\(D_1, D_2, ..., D_i, ..., D_n\) \((0<=D_i<=N, D_i≠i)\)
输出格式:
一个整数,代表最大价值
咋一看十分的像背包,但里面的东西互相拧成一坨相互依赖之类的,我们需要稍微简化一下问题。
一个软件最多依赖另外一个软件,把被别人依赖的某个软件向依赖它的软件连上一条有向边,可以得出,每个点的入度均为1,这是啥?一棵树啊。
然而这样想就出现了问题,万一有环呢?好说,把环给缩掉就行了。我们把新出现的一个森林连上一个共同的虚根0,构成一颗树,于是问题就转换成了树形DP
需要注意的是,一个点要用它子树,当且仅当这个子树的根被选上时才可用。
\(dp[i][j]\)代表以\(i\)为根的树,在容量为\(j\)的时候,没有处理它的根,所得到的最大价值。
转移:\(dp[i][j]=max(dp[i][j],dp[i-k][j]+dp[son][k-w[son]]+v[son])\)
#include <cstdio>
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
const int N=104;
const int M=502;
struct Edge
{
int to,next;
}edge[N];
int head[N],cnt=0;
void add(int u,int v)
{
edge[++cnt].next=head[u];edge[cnt].to=v;head[u]=cnt;
}
int n,m,w[N],v[N];
int time=0,low[N],dfn[N],vis[N],s[N],tot=0,ha[N];
int n0=0,w0[N],v0[N],in[N],g[N][N];
void tarjan(int now)
{
low[now]=dfn[now]=++time;
s[++tot]=now;
vis[now]=1;
for(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],dfn[v]);
}
if(dfn[now]==low[now])
{
int k;n0++;
do
{
k=s[tot--];
vis[k]=0;
ha[k]=n0;
w0[n0]+=w[k];
v0[n0]+=v[k];
}while(k!=now);
}
}
int dp[N][M];//以i为根(不装)的子树装j时的最大价值
void dfs(int now)
{
for(int i=1;i<=n0;i++)
if(g[now][i])
{
dfs(i);
for(int j=m;j>=w0[i];j--)
for(int k=w0[i];k<=j;k++)
dp[now][j]=max(dp[now][j],dp[i][k-w0[i]]+v0[i]+dp[now][j-k]);
}
}
int main()
{
scanf("%d%d",&n,&m);int to;
for(int i=1;i<=n;i++) scanf("%d",w+i);
for(int i=1;i<=n;i++) scanf("%d",v+i);
for(int i=1;i<=n;i++) {scanf("%d",&to);if(to) add(to,i);}
for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;i++)
for(int j=head[i];j;j=edge[j].next)
{
int v=edge[j].to;
if(ha[i]!=ha[v]&&!g[ha[i]][ha[v]])
g[ha[i]][ha[v]]=1,in[ha[v]]++;
}
for(int i=1;i<=n0;i++)
if(!in[i]) g[0][i]=1;;
dfs(0);
printf("%d\n",dp[0][m]);
return 0;
}
2018.6.13