P2515 [HAOI2010]軟體安裝
題目描述
現在我們的手頭有 \(N\) 個軟體,對於一個軟體 \(i\) ,它要佔用 \(W_i\)的磁碟空間,它的價值 \(V_i\)。我們希望從中選擇一些軟體安裝到一臺磁碟容量為 \(M\) 計算機上,使得這些軟體的價值儘可能大(即 \(V_i\)的和最大)。
但是現在有個問題:軟體之間存在依賴關係,即軟體i只有在安裝了軟體 \(j\)(包括軟體 \(j\) 的直接或間接依賴)的情況下才能正確工作(軟體 \(i\) 依賴軟體 \(j\) )。幸運的是,一個軟體最多依賴另外一個軟體。如果一個軟體不能正常工作,那麼它能夠發揮的作用為 \(0\) 。
我們現在知道了軟體之間的依賴關係:軟體 \(i\)
輸入格式
第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;
}