【Luogu P2515】软件安装

孤街浪徒 提交于 2019-12-04 06:09:33

Luogu P2515
这道题的题面与P2146有点像。一些不同地方就是P2146是无环的,这题是有环的。
很显然,如果有几个软件的依赖关系形成环,那么这几个软件就可以被看成是一个大软件,其价值和空间都是原先的总和。
那么,我们就可以利用Tarjan算法求强连通分量+缩点,最后加一个树上的背包就可以了。
注意,缩点后的图不一定是一棵树,但是我们可以人为的加入一个权值为零的根节点,连接所有入度为0的点。
有关Tarjan算法和强连通分量:
【Luogu P3387】缩点模板(强连通分量Tarjan&拓扑排序)

#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
const int maxn=110,maxm=510;
int dfn[maxn],low[maxn],stk[maxn],cnt,tim,d[maxn],tot,sccw[maxn],sccv[maxn];
int w[maxn],v[maxn],scc[maxn],in[maxn],ohead[maxn],ocnt,m,n,head[maxn],dp[maxn][maxm],ans;
struct data
{
    int to,next;
}oe[maxn],e[maxn];
bool vis[maxn];
void Tarjan(int now)
{
    dfn[now]=low[now]=++tim;
    stk[++cnt]=now;
    vis[now]=true;
    for (int i=head[now];i;i=e[i].next)
    { 
        if (!dfn[e[i].to])
        {
            Tarjan(e[i].to);
            low[now]=min(low[now],low[e[i].to]);
        }
        else
        {
            if (vis[e[i].to]) low[now]=min(low[now],dfn[e[i].to]);
        }
    }
    if (dfn[now]==low[now])
    {
        tot++;
        while (true)
        {
            scc[stk[cnt]]=tot;
            sccv[tot]+=v[stk[cnt]];
            sccw[tot]+=w[stk[cnt]];
            vis[stk[cnt]]=false;
            cnt--;
            if (stk[cnt+1]==now) break;
        }
    }
}
void dfs(int now)//树上背包
{
    //基本思路如下:枚举子树能取的空间,再利用01背包的原理进行状态转移。
    //还有一种方法可以把多叉树转化成二叉树,按照左儿子又兄弟的原则重新建树。实现起来比较麻烦,所以没写。
    for (int i=sccw[now];i<=m;i++) dp[now][i]=sccv[now];//初始值
    for (int i=ohead[now];i;i=oe[i].next)
    {
        int to=oe[i].to;
        dfs(to);
        for (int j=m-sccw[now];j>=0;j--) 
        {
            for (int k=0;k<=j;k++)
                dp[now][j+sccw[now]]=max(dp[now][j+sccw[now]],dp[now][j+sccw[now]-k]+dp[to][k]);
        }
    }
}
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",&d[i]);
        e[i].to=i;
        e[i].next=head[d[i]];
        head[d[i]]=i;
        //注意建边的方向。
    }
    cnt=0;
    for (int i=1;i<=n;i++) if (!dfn[i]) Tarjan(i);
    ocnt=0;
    for (int i=1;i<=n;i++)
        for (int j=head[i];j;j=e[j].next)
        if (scc[i]!=scc[e[j].to])
        {
            oe[++ocnt].to=scc[e[j].to];
            oe[ocnt].next=ohead[scc[i]];
            ohead[scc[i]]=ocnt;
            in[scc[e[j].to]]++;//统计入度
        }
    tot++;//人为加入的根节点
    for (int i=1;i<tot;i++)
        if (in[i]==0)
        {
            oe[++ocnt].to=i;
            oe[ocnt].next=ohead[tot];
            ohead[tot]=ocnt;//连边。
        }
    dfs(tot);
    printf("%d\n",dp[tot][m]);
    return 0;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!