1. 程式人生 > 實用技巧 >P2515 [HAOI2010]軟體安裝

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;
}