bzoj2427:[HAOI2010]软件安装(Tarjan+tree_dp)

2427: [HAOI2010]软件安装

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 1053  Solved: 424
[Submit][Status][Discuss]

Description

现在我们的手头有N个软件,对于一个软件i,它要占用Wi的磁盘空间,它的价值为Vi。我们希望从中选择一些软件安装到一台磁盘容量为M计算机上,使得这些软件的价值尽可能大(即Vi的和最大)。

但是现在有个问题:软件之间存在依赖关系,即软件i只有在安装了软件j(包括软件j的直接或间接依赖)的情况下才能正确工作(软件i依赖软件j)。幸运的是,一个软件最多依赖另外一个软件。如果一个软件不能正常工作,那么它能够发挥的作用为0。

我们现在知道了软件之间的依赖关系:软件i依赖软件Di。现在请你设计出一种方案,安装价值尽量大的软件。一个软件只能被安装一次,如果一个软件没有依赖则Di=0,这时只要这个软件安装了,它就能正常工作。

 

Input

第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 )

 

Output

一个整数,代表最大价值。

 

Sample Input

3 10
5 5 6
2 3 4
0 1 1

Sample Output

5

HINT

 

Source

Day2

 

/*
开始一看这不是个基础的树型动规吗?(知道基础,但我不会啊),但是一看还有环嘞,苦逼了... 
根据依赖关系可以画出来一张图,有三种可能的情况:
1.依赖关系构成一棵树。 
2.依赖关系构成一个环。 
3.依赖关系构成一个环下面吊着一棵树。
因为有2,3这些情况,所以要先有tarjan预处理一下,缩环为点,重新建图。
对于建好的图,跑一边树形背包即可,思想类似于01背包
f[x][tot]表示以x为根,容量为tot的最大收益。把x的各个子树看成物品
再枚举每个子树所分给的容量,tot从大到小转移。
*/
#include<iostream>
#include<cstdio>
#include<cstring>

using namespace std;
int n,m,cnt,scc,ind,top,num;
int v[105],w[105];
int sv[105],sw[105];bool in_stack[105];
int dfn[105],low[105],belong[105];
int stack[105],f[105][505],in[505],head[505],head2[505];
struct node
{
    int from;
    int to;
    int next;
}e[1010],e2[1010];

inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

inline void insert(int from,int to)
{
    e[++num].from=from;
    e[num].to=to;
    e[num].next=head[from];
    head[from]=num;
}

inline void insert2(int from,int to)
{
    in[to]=1;
    e2[++num].from=from;
    e2[num].to=to;
    e2[num].next=head2[from];
    head2[from]=num; 
}

void Tarjan(int u)
{
    int now=0;
    dfn[u]=low[u]=++ind;
    stack[++top]=u;
    in_stack[u]=1;
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(!dfn[v])
        {
            Tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(in_stack[v]) low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u])
    {
        scc++;
        while(now!=u)//统计环 
        {
            now=stack[top--];in_stack[now]=0;
            belong[now]=scc;
            sw[scc]+=w[now];
            sv[scc]+=v[now];
        }
    }
}
void rebuild()//重建图 
{
    num=0;
    for(int x=1;x<=n;x++)
      for(int i=head[x];i;i=e[i].next)
      {
          int v=e[i].to;
          if(belong[v]!=belong[x])
            insert2(belong[x],belong[v]);
      }
}
void dp(int x)
{
    for(int i=head2[x];i;i=e2[i].next)
    {
        dp(e2[i].to);
        for(int j=m-sw[x];j>=0;j--) 
        {
            for(int k=0;k<=j;k++)//枚举子树的的限制。
                f[x][j]=max(f[x][j],f[x][k]+f[e2[i].to][j-k]);        
        }
    }
    for(int j=m;j>=0;j--)//完全背包 
    {
        if(j>=sw[x])f[x][j]=f[x][j-sw[x]]+sv[x];
        else f[x][j]=0;
    }
}

int main()
{
    n=read();m=read();
    for(int i=1;i<=n;i++)w[i]=read();
    for(int i=1;i<=n;i++)v[i]=read();
    for(int i=1;i<=n;i++)
    {
        int x=read();
        if(x)insert(x,i);
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i])Tarjan(i);
    rebuild();
    for(int i=1;i<=scc;i++)
        if(!in[i])
            insert2(scc+1,i);//这个地方要加1,因为根节点属于新的环。(不确定) 
    dp(scc+1);
    printf("%d\n",f[scc+1][m]);
    return 0;
} 

 

posted @ 2017-02-07 20:49  安月冷  阅读(172)  评论(0编辑  收藏  举报