P2515 [HAOI2010]软件安装

Link

题目描述

现在我们的手头有 \(N\) 个软件,对于一个软件 \(i\) ,它要占用 \(W_i\)的磁盘空间,它的价值 \(V_i\)。我们希望从中选择一些软件安装到一台磁盘容量为 \(M\) 计算机上,使得这些软件的价值尽可能大(即 \(V_i\)的和最大)。

但是现在有个问题:软件之间存在依赖关系,即软件i只有在安装了软件 \(j\)(包括软件 \(j\) 的直接或间接依赖)的情况下才能正确工作(软件 \(i\) 依赖软件 \(j\) )。幸运的是,一个软件最多依赖另外一个软件。如果一个软件不能正常工作,那么它能够发挥的作用为 \(0\)

我们现在知道了软件之间的依赖关系:软件 \(i\) 依赖软件 \(D_i\)。现在请你设计出一种方案,安装价值尽量大的软件。一个软件只能被安装一次,如果一个软件没有依赖则 \(D_i=0\),这时只要这个软件安装了,它就能正常工作。

输入格式

第1行:\(N,M(0\leq N\leq 100, 0\leq M\leq 500)\)

第2行:\(W_1,W_2, ... W_i, ..., W_n (0\leq W_i\leq M)\)

第3行:\(V_1, V_2, ..., V_i, ..., V_n (0\leq V_i\leq 1000)\)

第4行 \(D_1, D_2, ..., D_i, ..., D_n (0\leq D_i\leq 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\n",f[0][m]);
	return 0;
}
posted @ 2020-09-12 21:22  genshy  阅读(117)  评论(0编辑  收藏  举报