BZOJ 2427 软件安装 (tarjan 树形依赖背包)

2427: [HAOI2010]软件安装

Time Limit: 10 Sec Memory Limit: 128 MB
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

思路:
根据题目,我们建立图。
显然这个图由一些树和一些scc构成(注意:scc一定不在树上),那么我们可以知道,如果选了scc中的一个点,其他点必须也要选,所以我们把所有的scc缩成一个点,这样就构成了一个森林。
对于一个入度为0的点,我们从一个虚点向其连接一条边,这样图就变成了树。
考虑树形dp,定义f [ i ][ j ]为对于i为根的子树总共分配j点权值能拿到的最大value。按照拓扑序做背包就行了。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#define N 505
#define INF 0x3f3f3f3f
using namespace std;

struct Edge{
    int to, next;
}ed1[2 * N], ed2[2 * N];

int idc1, head[N], idc2, head2[N], in[N];

void adde1( int u, int v ) {
    ed1[++idc1].to = v;
    ed1[idc1].next = head[u];
    head[u] = idc1;
}
void adde2( int u, int v ) {
    in[v] = 1;
    ed2[++idc2].to = v;
    ed2[idc2].next = head2[u];
    head2[u] = idc2;
}

int idx, dfn[N], low[N], ins[N], stack[N], top;
int n, m, w[N], v[N], x, f[105][505];
int scc, sv[N], sw[N], place[N];

void tarjan( int u ) {
    dfn[u] = low[u] = ++idx;
    ins[u] = 1;
    stack[++top] = u;
    for ( int i = head[u]; i; i = ed1[i].next ) {
        int v = ed1[i].to;
        if ( !dfn[v] ) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if (ins[v]) low[u] = min(low[u], dfn[v]);
    }
    if( low[u] == dfn[u] ) {
        scc++; int now = 0;
        while( now != u ) {
            now = stack[top--];
            ins[now] = 0;
            place[now] = scc;
            sv[scc] += v[now];
            sw[scc] += w[now];
        }
    }
}

void dp( int u ) {
    for ( int i = 0; i < sw[u]; i++ ) f[u][i] = 0;
    for ( int i = sw[u]; i <= m; i++ ) f[u][i] = sv[u];
    for ( int i = head2[u]; i; i = ed2[i].next ) {
        int v = ed2[i].to;
        dp( v );
        for ( int k = m; k >= sw[u]; k-- )
            for ( int j = 0; j <= k-sw[u]; j++ )
                f[u][k] = max(f[u][k], f[u][k-j]+f[v][j]);
    }
}

int main() {
    scanf( "%d%d", &n, &m );
    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", &x );
        if ( x != 0 )
        adde1( x, 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=ed1[j].next ) {
            int v = ed1[j].to;
            if ( place[v] != place[i] )
            adde2(place[i], place[v]);
        }
    for ( int i=1; i<=scc; i++ )
        if ( !in[i] ) adde2(scc+1, i);
    dp(scc+1);//虚点 
    printf( "%d\n", f[scc+1][m] );
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值