|BZOJ 2427|树形DP|强连通分量|[HAOI2010]软件安装

BZOJ传送门
根据题目可以构造一幅图,可以得知这个图是一些森林和环,我们对图缩点,建立虚结点,使所有没有入度的强连通分量连接虚结点,再进行树上背包即可。

#include<cstdio>  
#include<algorithm>  
#include<cstring>  
#include<vector>
#include<stack>
#define ms(i,j) memset(i,j, sizeof i);
using namespace std;
const int MAXN = 100 + 5, MAXM = 500 + 5;
int n,m;
int wi[MAXN], vi[MAXN];
vector<int> G[MAXN];

int s_size = 0, s_no[MAXN], s_wi[MAXN], s_vi[MAXN], ino[MAXN];
vector<int> RG[MAXN];

stack<int> s;
int ex[MAXN], sz = 0, dn[MAXN], low[MAXN];
void tarjan(int u)
{
    dn[u] = low[u] = ++sz;
    ex[u] = -1;
    s.push(u);
    for (int i=0;i<G[u].size();i++)
    {
        int v = G[u][i];
        if (!ex[v])
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        } else if (ex[v]==-1)
        {
            low[u] = min(low[u], dn[v]);
        }
    }
    if (low[u]==dn[u])
    {
        s_size++;
        int e;
        do
        {
            e = s.top(); s.pop();
            s_no[e] = s_size;
            s_wi[s_size] += wi[e];
            s_vi[s_size] += vi[e];
            ex[e] = 1;
        } while(e!=u);
    }
}
void rebuild()
{
    ms(ino,0);
    for (int u=0;u<=n;u++)
    {
        for (int j=0;j<G[u].size();j++)
        {
            int v = G[u][j];
            if (s_no[v]!=s_no[u])
            {
                RG[s_no[u]].push_back(s_no[v]);
                ino[s_no[v]]++;
            }
        }
    }
    for (int i=1;i<=s_size;i++)
    if (!ino[i]&&s_no[0]!=i) RG[s_no[0]].push_back(i);
}
int f[MAXN][MAXM];
void dp(int u)
{
    for (int i=0;i<RG[u].size();i++)
    {
        int v = RG[u][i];
        if (!ex[v])
        {
            ex[v] = true;
            dp(v);
            for (int j=m-s_wi[u];j>=0;j--)
            for (int k=0;k<=j;k++)
            f[u][j] = max(f[u][j], f[u][j-k]+f[v][k]);
        }
    }
    for (int j=m;j>=0;j--)
    {
        if (j>=s_wi[u]) f[u][j] = f[u][j-s_wi[u]] + s_vi[u];
        else f[u][j] = 0;
    }
}
int main()  
{  
    scanf("%d%d", &n,&m); wi[0] = vi[0] = 0;
    for (int i=1;i<=n;i++) scanf("%d", &wi[i]);
    for (int i=1;i<=n;i++) scanf("%d", &vi[i]);
    for (int i=1;i<=n;i++)
    {
        int di;
        scanf("%d", &di);
        G[di].push_back(i);
    }
    ms(ex,0); ms(s_wi,0); ms(s_vi,0);
    for (int i=0;i<=n;i++) if (!ex[i]) tarjan(i);
    rebuild();
    ms(ex,0); ms(f,0); dp(s_no[0]);
    printf("%d\n", f[s_no[0]][m]);
    return 0;  
}  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值