【洛谷P2515【HAOI2010】】软件安装

题目描述

现在我们的手头有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 (0<=Wi<=M )

第3行:V1, V2, ..., Vi, ..., Vn (0<=Vi<=1000 )

第4行:D1, D2, ..., Di, ..., Dn (0<=Di<=N, Di≠i )

输出格式:

一个整数,代表最大价值

输入输出样例

输入样例#1

3 10
5 5 6
2 3 4
0 1 1

输出样例#1

5

 

 

算法:

树形DP

 

分析:

一看到这道题,就感觉和选课很相似,不过再仔细一看,就发现其实之间大有不同。

选课一题,是不可以成环的,因为先修课的先修课不可能成为后面的后修课,所以,判环是没必要的。

但是这道题,非常坑爹,它可以出现环。若出现a依赖b,b依赖c,c依赖a,那么我们究竟怎么搜索呢,而且转移方程也不好写。

我们通过观察发现,这种环状的问题,要是选了其中一个,那么就意味着要把整个环给选了,要么不选,就得整个都不选。

所以,我们就可以从这里入手,把一个个的环缩成一个个新的点,一个点代表了环的整体。

接下来就可以通过选课的后续思路,建造一棵左儿子右兄弟的二叉树,通过多叉树转二叉树进行记忆化搜索。

 

后面的就很简单了,状态转移方程就是要不要当前的兄弟,还是把机会分给兄弟和孩子。

 

不过中途有很多小点需要注意。

判环要用floyed的方法三重for来判断环,记得三个循环的变量顺序不能错,否则后果会判不到环。

其次,缩点建点的时候,主体分三个情况来判断:

1、这是新环,我需要建点来记录,他的价值和体积我都直接相加就好了。不过我们要记得把已经转移的新点的原来两个旧点序号变成负数,而新点的下标+旧点的内容恰好是等于n的,每一个环一一对应,不多也不会少,详情见代码操作。

2、这是旧环,我发现了新的点,这个点在环内。那么我就把这个点并到环中,把价值和体积也加到新点上,然后把原来这个旧环的密码推到这个新点上。

3、找到一个新点,他和一个旧环有联系,但不属于这个环的一部分,于是把它的从属关系推到新的点上。

 

 

步骤简化为:判环+缩点+多叉转二叉+记忆化搜索。

 

上代码:

 

  1 #include<cstdio>
  2 #include<iostream>
  3 #include<cctype>
  4 using namespace std;
  5 
  6 const int size=600;
  7 int n,m,ans,sum,d[size],v[size],w[size],f[size][4*size],b[size],c[size];
  8 bool a[size][size];                                //是否连通
  9 
 10 inline int read()                                //读入优化
 11 {
 12     int f=1,x=0;
 13     char c=getchar();
 14     while (!isdigit(c))
 15         f=c=='-'?-1:1,c=getchar();
 16     while (isdigit(c))
 17         x=(x<<1)+(x<<3)+(c^48),c=getchar();
 18     return x*f;
 19 }
 20 
 21 void floyed()                                    //Floyd判环
 22 {
 23     int i,j,k;
 24     for (k=1;k<=n;k++)
 25         for (i=1;i<=n;i++)
 26             for (j=1;j<=n;j++)
 27                 if (a[j][k]&&a[k][i])
 28                     a[j][i]=1;
 29 }
 30 
 31 void connect()                                //缩点建点
 32 {
 33     ans=n;                                //ans表示新点的下标
 34     int i,j;
 35     for (i=1;i<=ans;i++)
 36         for (j=1;j<=ans;j++)
 37         {
 38             if (a[i][j]&&a[j][i]&&i!=j&&w[i]>0&&w[j]>0)        //发现新环
 39             {
 40                 sum--;                    //sum是旧点的密码
 41                 ans++;
 42                 w[ans]=w[i]+w[j];
 43                 v[ans]=v[i]+v[j];
 44                 w[i]=w[j]=sum;
 45             }
 46             else
 47             if (w[d[j]]<0&&w[j]>0)                    //发现旧环
 48             {
 49                 if (a[d[j]][j]&&a[j][d[j]])                //属于环
 50                 {
 51                     w[n-w[d[j]]]+=w[j];
 52                     v[n-w[d[j]]]+=v[j];
 53                     w[j]=w[d[j]];
 54                 }
 55                 else                                    //不属于环
 56                 if (a[d[j]][j]&&!a[j][d[j]]||!a[d[j]][j]&&a[j][d[j]])
 57                     d[j]=n-w[d[j]];
 58             }
 59         }
 60 }
 61 
 62 int dfs(int root,int val)                                //记忆化搜索
 63 {
 64     if (f[root][val]>0)
 65         return f[root][val];
 66     if (root==0||val<=0)
 67         return 0;
 68     f[b[root]][val]=dfs(b[root],val);
 69     f[root][val]=f[b[root]][val];
 70     int i,k=val-w[root];
 71     for (i=0;i<=k;i++)
 72     {
 73         f[b[root]][k-i]=dfs(b[root],k-i);
 74         f[c[root]][i]=dfs(c[root],i);
 75         f[root][val]=max(f[root][val],v[root]+f[b[root]][k-i]+f[c[root]][i]);
 76     }
 77     return f[root][val];
 78 }
 79 
 80 int main()
 81 {
 82     int i;
 83     n=read();
 84     m=read();
 85     for (i=1;i<=n;i++)
 86         w[i]=read();
 87     for (i=1;i<=n;i++)
 88         v[i]=read();
 89     for (i=1;i<=n;i++)
 90     {
 91         d[i]=read();
 92         a[d[i]][i]=1;
 93     }
 94     floyed();
 95     connect();
 96     for (i=1;i<=ans;i++)                            //模拟链表
 97         if (w[i]>0)
 98         {
 99             b[i]=c[d[i]];
100             c[d[i]]=i;
101         }
102     printf("%d",dfs(c[0],m));
103     return 0;
104 }

 

这道题其实难度很大,不过只要把原理弄懂,代码还是很好打的,主要就是围绕着如何更加简便地进行记忆化搜索,然后一层层地进行优化,这道题用到的就是判环和缩点的知识。

 

嗯,就这样了。

posted @ 2018-02-15 18:11  MN2016  阅读(172)  评论(0编辑  收藏  举报