HAOI 2010 软件安装(Tarjan+树形dp)

【HAOI2010 Day1】软件安装

问题描述

现在我们的手头有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
第3行:V1,V2, … Vi, … ,Vn
第4行:D1,D2, … Di, … ,Dn

输出格式

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

样例输入

3 10
5 5 6
2 3 4
0 1 1

样例输出

5


此题看上去是裸的树形dp,然而,软件的依赖关系形成的是一个图,这个图可能有多个联通块,还可能存在环,因此需要用Tarjan缩点,然后添加一个虚拟的原点,向每一个缩点后的树根连边,构成一棵树,然后跑dp就好。

关于dp,将每个点看成一个 MWi 的背包,然后再把自己加进去就行了。


代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stack>
#define N 1005
using namespace std;
int n,m,W[N],V[N],NW[N],NV[N],D[N];
int scc,VT,BE[N],low[N],dfn[N];
int TOT,LA[N],NE[N],ST[N],EN[N];
int tot,la[N],ne[N],en[N];
int F[N][N];
bool mark[N],map[N][N];
stack<int>S;
void ADD(int x,int y)
{
    TOT++;
    ST[TOT]=x;
    EN[TOT]=y;
    NE[TOT]=LA[x];
    LA[x]=TOT;
}
void add(int x,int y)
{
    tot++;
    en[tot]=y;
    ne[tot]=la[x];
    la[x]=tot;
}
void Tarjan(int u)
{
    int i,v;
    dfn[u]=low[u]=++VT;
    S.push(u);mark[u]=1;
    for(i=LA[u];i;i=NE[i])
    {
        v=EN[i];
        if(!dfn[v])
        {
            Tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(mark[v])low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u])
    {
        scc++;
        do{
            v=S.top();
            S.pop();
            mark[v]=0;
            BE[v]=scc;
            NW[scc]+=W[v];
            NV[scc]+=V[v];
        }while(u!=v);
    }
}
void DP(int x)
{
    int i,j,k,y;
    for(i=la[x];i;i=ne[i])
    {
        y=en[i];
        DP(y);
        for(j=m-NW[x];j>=0;j--)
        for(k=j;k>=0;k--)F[x][j]=max(F[x][j],F[x][k]+F[y][j-k]);
    }
    for(i=m;i>=0;i--)
    if(i>=NW[x])F[x][i]=F[x][i-NW[x]]+NV[x];
    else F[x][i]=0;
}
int main()
{
    int i,j,x,y,ans=0;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++)scanf("%d",&W[i]);
    for(i=1;i<=n;i++)scanf("%d",&V[i]);
    for(i=1;i<=n;i++)
    {
        scanf("%d",&x);
        if(x)ADD(x,i);
    }
    for(i=1;i<=n;i++)if(!dfn[i])Tarjan(i);
    for(i=1;i<=TOT;i++)
    {
        x=ST[i];y=EN[i];
        if(BE[x]!=BE[y])add(BE[x],BE[y]),D[BE[y]]++;
    }
    BE[0]=scc+1;
    for(i=1;i<=scc;i++)if(!D[i])add(BE[0],i);
    DP(BE[0]);
    for(i=0;i<=m;i++)ans=max(ans,F[BE[0]][i]);
    printf("%d",ans);
}
这道题目还可以使用树状数组或线段树来实现,时间复杂度也为 $\mathcal{O}(n\log n)$。这里给出使用树状数组的实现代码。 解题思路: 1. 读入数据; 2. 将原数列离散化,得到一个新的数列 b; 3. 从右往左依次将 b 数列中的元素插入到树状数组中,并计算逆序对数; 4. 输出逆序对数。 代码实现: ```c++ #include <cstdio> #include <cstdlib> #include <algorithm> const int MAXN = 500005; struct Node { int val, id; bool operator<(const Node& other) const { return val < other.val; } } nodes[MAXN]; int n, a[MAXN], b[MAXN], c[MAXN]; long long ans; inline int lowbit(int x) { return x & (-x); } void update(int x, int val) { for (int i = x; i <= n; i += lowbit(i)) { c[i] += val; } } int query(int x) { int res = 0; for (int i = x; i > 0; i -= lowbit(i)) { res += c[i]; } return res; } int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) { scanf("%d", &a[i]); nodes[i] = {a[i], i}; } std::sort(nodes + 1, nodes + n + 1); int cnt = 0; for (int i = 1; i <= n; ++i) { if (i == 1 || nodes[i].val != nodes[i - 1].val) { ++cnt; } b[nodes[i].id] = cnt; } for (int i = n; i >= 1; --i) { ans += query(b[i] - 1); update(b[i], 1); } printf("%lld\n", ans); return 0; } ``` 注意事项: - 在对原数列进行离散化时,需要记录每个元素在原数列中的位置,便于后面计算逆序对数; - 设树状数组的大小为 $n$,则树状数组中的下标从 $1$ 到 $n$,而不是从 $0$ 到 $n-1$; - 在计算逆序对数时,需要查询离散化后的数列中比当前元素小的元素个数,即查询 $b_i-1$ 位置上的值; - 在插入元素时,需要将离散化后的数列的元素从右往左依次插入树状数组中,而不是从左往右。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值