题目描述
现在我们的手头有
N
N
N个软件,对于一个软件
i
i
i,它要占用
W
i
W_i
Wi的磁盘空间,它的价值为$V_i
。
我
们
希
望
从
中
选
择
一
些
软
件
安
装
到
一
台
磁
盘
容
量
为
。我们希望从中选择一些软件安装到一台磁盘容量为
。我们希望从中选择一些软件安装到一台磁盘容量为M
计
算
机
上
,
使
得
这
些
软
件
的
价
值
尽
可
能
大
(
即
计算机上,使得这些软件的价值尽可能大(即
计算机上,使得这些软件的价值尽可能大(即V_i
的
和
最
大
)
。
但
是
现
在
有
个
问
题
:
软
件
之
间
存
在
依
赖
关
系
,
即
软
件
i
只
有
在
安
装
了
软
件
的和最大)。 但是现在有个问题:软件之间存在依赖关系,即软件i只有在安装了软件
的和最大)。但是现在有个问题:软件之间存在依赖关系,即软件i只有在安装了软件j
(
包
括
软
件
j
的
直
接
或
间
接
依
赖
)
的
情
况
下
才
能
正
确
工
作
(
软
件
i
i
依
赖
软
件
(包括软件j的直接或间接依赖)的情况下才能正确工作(软件ii依赖软件
(包括软件j的直接或间接依赖)的情况下才能正确工作(软件ii依赖软件j$)。幸运的是,一个软件最多依赖另外一个软件。如果一个软件不能正常工作,那么它能够发挥的作用为
0
0
0。
我们现在知道了软件之间的依赖关系:软件i依赖软件
D
i
D_i
Di。现在请你设计出一种方案,安装价值尽量大的软件。一个软件只能被安装一次,如果一个软件没有依赖则
D
i
=
0
D_i=0
Di=0,这时只要这个软件安装了,它就能正常工作。
输入格式
第1行:
N
,
M
(
0
≤
N
≤
100
,
0
≤
M
≤
500
)
N,M(0≤N≤100,0≤M≤500)
N,M(0≤N≤100,0≤M≤500)
第2行: W 1 , W 2 , . . . W i , . . . , W n ( 0 ≤ W i ≤ M ) W_1,W_2, ... W_i, ..., W_n (0≤W i ≤M) W1,W2,...Wi,...,Wn(0≤Wi≤M)
第3行: V 1 , V 2 , . . . , V i , . . . , V n ( 0 ≤ V i ≤ 1000 ) V_1, V_2, ..., V_i, ..., V_n (0≤V i≤1000) V1,V2,...,Vi,...,Vn(0≤Vi≤1000)
第4行: D 1 , D 2 , . . . , D i , . . . , D n ( 0 ≤ D i ≤ N , D i = i ) D_1, D_2, ..., D_i, ..., D_n (0≤D_i≤N,D_i =i) D1,D2,...,Di,...,Dn(0≤Di≤N,Di=i)
输出格式
一个整数,代表最大价值
解题思路
同样,这道题目类似是一个依赖的问题,是一道动态规划。但是它确实是树规么?
我们来想这样一组数据, 1 1 1依赖 2 2 2, 2 2 2依赖 3 3 3, 3 3 3依赖 1 1 1。这样符合题目要求,但有形成了环,所以不是一棵树了。但是根据题目,这样特殊的情况,要么全要,要么全就不要。所以,事实上我们可以将这个环看成一个点再来动规,即缩点。如何判断是否是一个环呢,依照数据范围,我们想到了floyed(弗洛里德),这是在这种数据范围内性价比最高的方式。最后树规。于是一个比较清晰的步骤就出来了:判环,缩点,树规。
接下来是细节:
首先存树,可以用邻接矩阵。
做
f
l
o
y
e
d
floyed
floyed:如果两点之间mapp[i][j]中有另一条路径相连,即
(
m
a
p
p
[
i
]
[
k
]
=
1
(mapp[i][k]=1
(mapp[i][k]=1 &&
m
a
p
p
[
k
]
[
j
]
=
1
)
mapp[k][j]=1)
mapp[k][j]=1)(
1
1
1表示两点是通的);那么
m
a
p
p
[
i
]
[
j
]
mapp[i][j]
mapp[i][j]也是通的且是环。
缩点:这个是最麻烦的,麻烦在于我们要把缩的点当成一个新点来判断,而且要判断某个点是否在某个环里。我们用染色法来判断,用所占的空间 w w w控制颜色的对应,有以下三种情况:
- 点i所在的环之前没有判断过,是新环。那么,我们将这个新环放到数组最后,即新加一个点,然后让这两个点的空间标记为负值 t m p tmp tmp,且 t m p + tmp+ tmp+ n n nn nn(新点的下标)等于原来的点数 n n n,这样,我们就可以通过某个点的空间迅速找到他所在的新点。
- 点j所在的环之前已经判断过了,是旧环(已合成新点),且 j j j是环的一部分。那么我们就把 j j j也加到这个新点里面,即体积,价值相加即可;
- 点j依赖的点所在的环是旧环,但是 j j j不是环的一部分(例如 1 1 1依赖 2 , 2 2,2 2,2依赖 3 , 3 3,3 3,3依赖 1 1 1。 4 4 4也依赖 1 1 1,那么, 4 4 4所在的是个环,但 4 4 4不属于环的一部分)。那么,把j的指向他依赖的点所在的环上 d [ j ] = n − w [ d [ j ] ] d[j]= n-w[d[j]] d[j]=n−w[d[j]]。
代码
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int n,m,nn,w[1000010],v[1000010],d[1000010],mapp[2010][2010],b[1000010],c[1000010],f[2010][2010],tmp;
void floyed(){//弗洛里德判断是否有环
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
if(mapp[i][j]==1&&mapp[k][i]==1)
mapp[k][j]=1;
}
void merge(){
nn=n;
for(int i=1;i<=nn;i++)
{
for(int j=1;j<=nn;j++)
{
if(mapp[i][j]==1&&mapp[j][i]==1&&i!=j&&w[i]>0&&w[j]>0)//如果是新环;
{
w[++nn]=w[i]+w[j];
v[nn]=v[i]+v[j];
tmp--;
w[i]=tmp;
w[j]=tmp;//tm+ nn(新点的下标) 等于原来的点数$n$
}
if(mapp[d[j]][j]==1&&mapp[j][d[j]]==1&&w[d[j]]<0&&w[j]>0)//如果j依赖的点被合并(是旧环),且j在环里
{
w[n-w[d[j]]]+=w[j];
v[n-w[d[j]]]+=v[j];
w[j]=w[d[j]];
}
if(w[d[j]]<0&&w[j]>0)//如果j依赖的点在环里,但是j不在环里
{
if((mapp[d[j]][j]==1&&mapp[j][d[j]]==0)||(mapp[d[j]][j]==0&&mapp[j][d[j]]==1))
d[j]=n-w[d[j]];
}
}
}
}
int dfs(int dep,int bsy){
if(f[dep][bsy]>0) return f[dep][bsy];
if(dep==0||bsy<=0) return 0;
f[b[dep]][bsy]=dfs(b[dep],bsy);
f[dep][bsy]=f[b[dep]][bsy];
//不取x
int y=bsy-w[dep];
for(int i=0;i<=y;i++)
{
f[c[dep]][y-i]=dfs(c[dep],y-i);
f[b[dep]][i]=dfs(b[dep],i);
f[dep][bsy]=max(f[dep][bsy],v[dep]+f[c[dep]][y-i]+f[b[dep]][i]);
}
//取x
return f[dep][bsy];
}
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]);
mapp[d[i]][i]=1;
}
floyed();
merge();
for(int i=1;i<=nn;i++)
{
if(w[i]>0)
{
b[i]=c[d[i]];
c[d[i]]=i;
}
}//反置
printf("%d",dfs(c[0],m));
}