题目描述
现在我们的手头有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 }
这道题其实难度很大,不过只要把原理弄懂,代码还是很好打的,主要就是围绕着如何更加简便地进行记忆化搜索,然后一层层地进行优化,这道题用到的就是判环和缩点的知识。
嗯,就这样了。