题目描述
现在我们的手头有 (N) 个软件,对于一个软件 (i) ,它要占用 (W_i)的磁盘空间,它的价值 (V_i)。我们希望从中选择一些软件安装到一台磁盘容量为 (M) 计算机上,使得这些软件的价值尽可能大(即 (V_i)的和最大)。
但是现在有个问题:软件之间存在依赖关系,即软件i只有在安装了软件 (j)(包括软件 (j) 的直接或间接依赖)的情况下才能正确工作(软件 (i) 依赖软件 (j) )。幸运的是,一个软件最多依赖另外一个软件。如果一个软件不能正常工作,那么它能够发挥的作用为 (0) 。
我们现在知道了软件之间的依赖关系:软件 (i) 依赖软件 (D_i)。现在请你设计出一种方案,安装价值尽量大的软件。一个软件只能被安装一次,如果一个软件没有依赖则 (D_i=0),这时只要这个软件安装了,它就能正常工作。
输入格式
第1行:(N,M(0leq Nleq 100, 0leq Mleq 500))
第2行:(W_1,W_2, ... W_i, ..., W_n (0leq W_ileq M))
第3行:(V_1, V_2, ..., V_i, ..., V_n (0leq V_ileq 1000))
第4行 (D_1, D_2, ..., D_i, ..., D_n (0leq D_ileq N, D_i≠i))
输出格式
一个整数,代表最大价值
输入输出样例
输入 #1
3 10
5 5 6
2 3 4
0 1 1
输出 #1
5
一个比较显然的问题就是如果我们按照依赖关系建边的话,会得到一棵树(对没有依赖的点建个超级源)。
剩下的就是树形背包的裸题啦。
但,当你兴奋的交上去,认为又能水一道题的时候,却发现你 (WA) 了。
因为 依赖关系可能会成为一个环,这就需要我们的第二个知识点, (tarjian)
(tarjian) 缩完点之后,我们就可以得到一个有向无环图,在向没有依赖的点建个超级源,这就变成了我们熟悉的树上背包问题。
一个需要注意的点就是,一定要在缩完点之后,在和超级源连边,否则可能会把超级源也给算进去。
还有就是 (f) 数组一定要赋初值,我就在这里卡了好几回。
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1010;
int n,m,x,cnt,sum,tot,top,num;
int head[N],hed[N],a[N],b[N],w[N],c[N];
int low[N],dfn[N],sta[N],shu[N],du[N],f[110][510];
bool vis[N];
inline int read()
{
int s = 0, w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s =s * 10+ch - '0'; ch = getchar();}
return s * w;
}
struct node
{
int to,net;
}e[N<<1],e2[N<<1];
void add(int x,int y)
{
e[++tot].to = y;
e[tot].net = head[x];
head[x] = tot;
}
void Add(int x,int y)
{
e2[++sum].to = y;
e2[sum].net = hed[x];
hed[x] = sum;
}
void tarjain(int x)
{
dfn[x] = low[x] = ++num;
sta[++top] = x; vis[x] = 1;
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if(!dfn[to])
{
tarjain(to);
low[x] = min(low[x],low[to]);
}
else if(vis[to])
{
low[x] = min(low[x],dfn[to]);
}
}
if(dfn[x] == low[x])
{
cnt++; int y;
do
{
y = sta[top--];
shu[y] = cnt;
c[cnt] += a[y];
w[cnt] += b[y];
vis[y] = 0;
}while(x != y);
}
}
void rebuild()
{
for(int i = 1; i <= n; i++)
{
for(int j = head[i]; j; j = e[j].net)
{
int to = e[j].to;
if(shu[i] != shu[to])
{
Add(shu[i],shu[to]);
du[shu[to]]++;
}
}
}
for(int i = 1; i <= cnt; i++)
{
if(du[i] == 0) Add(0,i);//没有依赖的点连向超级源
}
}
void dp(int x,int fa)
{
for(int i = 0; i < c[x]; i++) f[x][i] = -1e9;//这里一定要赋初值
for(int i = c[x]; i <= m; i++) f[x][i] = w[x];
for(int i = hed[x]; i; i = e2[i].net)
{
int to = e2[i].to;
if(to == fa) continue;
dp(to,x);
for(int j = m; j >= 0; j--)//01背包
{
for(int k = 0; k <= j; k++)//k正序倒叙都可以
{
// cout<<to<<" "<<k<<" "<<f[to][k]<<endl;
f[x][j] = max(f[x][j],f[x][j-k]+f[to][k]);
}
}
}
}
int main()
{
n = read(); m = read();
for(int i = 1; i <= n; i++) a[i] = read();
for(int i = 1; i <= n; i++) b[i] = read();
for(int i = 1; i <= n; i++)
{
x = read();
if(x == 0) continue;
else add(x,i);//连边
}
for(int i = 1; i <= n; i++)//缩点
{
if(!dfn[i]) tarjain(i);
}
rebuild();//重新建图
dp(0,0);//树上背包
printf("%d
",f[0][m]);
return 0;
}