天天看点

YBTOJ 软件安装

题面:​​洛谷传送门​​

题目算法要素:tarjan+树形dp

题目分析:

一、总体概括

可以发现一个环中的点必须同时被选择,因此很容易能想到要tarjan缩点。

缩点后形成一张DAG,由于题目的条件,d[i]=0表示一个软件没有另一个软件为前提,因此有一个超级源点0。可以考虑从0点开始,跑一遍树形dp(树上背包),即可得出答案。

二(细节一):为什么不能用拓扑dp求解:

如果跑一遍拓扑dp,可以得出以每一个点为终点的最大权链,但是各个链中显然有重合的可能性,因此无法直接通过再跑一遍01背包的方式将以每个点为终点的最大权链合成整张DAG的最优解。

因此拓扑dp显然不行。

这道题也告诉我们缩点不是一定要用拓扑dp求解的。

三(细节二):如何在树形dp中强制选择当前点

我们使用dp式:f[u][j]表示在以u根的子树中,耗费j的存储空间,能得到的软件的最大总价值。

但是别忘了只有选择了根节点,才能选择子树中的点。

因此我们加上一条限制条件:对于f[u][j],强制要求选择点u。

这其实是一类问题:如何在树形dp中强制选择一棵子树的根节点

肯定要考虑枚举j和子树费用k的过程(修改转移式)

如果没有这个条件,我们显然会把状态转移写成这个样子

令u的一个子节点为v
for(int j=sumw[u];j<=m;++j) f[u][j]=sumv[u]; 
for(int j=m;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=sumw[u];j<=m;++j) f[u][j]=sumv[u]; 
for(int j=m-sumw[u];j>=0;--j)
{
  for(int k=0;k<=j;++k)
    f[u][j+sumw[u]]=max(f[u][j+sumw[u]],f[u][j+sumw[u]-k]+f[v][k]);
}
      

由于每一个位置都一定给u留够了空间,且每次计算空间都加上sumw[u],另外,最开始的初始化是给每个未选择的空间都赋上了sumv[u],因此sumv[u]一定会被选择。

或者还有一种写法:

就是在子树不包括u的部分选择的空间一定比在子树包括u的部分选择的空间少一个sumw[u],也就是一定把u选择上了。

void dp(int now)
{
  for(int i=sumw[now];i<=m;++i) f[now][i]=sumv[now];
  for(int i=head2[now];~i;i=e2[i].nxt)
  {
    int v=e2[i].v;
    dp(v);
    for(int j=m;j>=sumw[now];--j)
    {
      for(int k=0;k<=j-sumw[now];++k)
      {
        f[now][j]=max(f[now][j],f[now][j-k]+f[v][k]);
      }
    }
  }
}
      

Code

#include<bits/stdc++.h>
using namespace std;
const int maxn=550;
int n,m,w[maxn],v[maxn],d[maxn],tot,col[maxn];
int dfn[maxn],low[maxn],Time,sumw[maxn],sumv[maxn];
int ecnt=-1,ecnt2=-1,head[maxn],head2[maxn];
int s[maxn],top,indu[maxn],f[105][505];
struct mint
{
  int nxt,u,v;  
}e[maxn],e2[maxn];
inline void addline(int u,int v)
{
  e[++ecnt].nxt=head[u];
  e[ecnt].u=u;
  e[ecnt].v=v;
  head[u]=ecnt;
}
inline void addline2(int u,int v)
{
  e2[++ecnt2].nxt=head2[u];
  e2[ecnt2].u=u;
  e2[ecnt2].v=v;
  head2[u]=ecnt2; 
}
void tarjan(int now)
{
  dfn[now]=low[now]=++Time;
  s[++top]=now;
  for(int i=head[now];~i;i=e[i].nxt)
  {
    int v=e[i].v;
    if(!dfn[v])
    {
      tarjan(v);
      low[now]=min(low[now],low[v]);  
    }
    else if(!col[v]) low[now]=min(low[now],dfn[v]);
  }
  if(dfn[now]==low[now])
  {
    tot++;
    int cur;
    do
    {
      cur=s[top--];
      col[cur]=tot;
      sumw[tot]+=w[cur];
      sumv[tot]+=v[cur];
    }
    while(cur!=now);
  }
}
void dp(int now)
{
  for(int i=sumw[now];i<=m;++i) f[now][i]=sumv[now];
  for(int i=head2[now];~i;i=e2[i].nxt)
  {
    int v=e2[i].v;
    dp(v);
    for(int j=m-sumw[now];j>=0;--j)
    {
      for(int k=0;k<=j;++k)
      {
        f[now][j+sumw[now]]=max(f[now][j+sumw[now]],f[now][j+sumw[now]-k]+f[v][k]);
      }
    }
  }
}
int main()
{
  scanf("%d%d",&n,&m);
  memset(head,-1,sizeof(head));
  memset(head2,-1,sizeof(head2));
  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]);
    addline(d[i],i);
  }
  for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i);
  for(int i=0;i<m;++i)
  {
    if(col[e[i].v] != col[e[i].u])
    {
      addline2(col[e[i].u],col[e[i].v]); 
      indu[col[e[i].v]]++;
    }
  }
  for(int i=1;i<=tot;++i) if(!indu[i]) addline2(0,i);
  dp(0);
  printf("%d",f[0][m]);
  return 0; 
}