题意简化
传送门
有 n 个物品和容量为 m 的背包,每个物品最多有一个先决条件物品 (即必须选了它的先决条件物品,才能选当前物品) ,第 i 个物品占 Wi 单位空间,有 Vi 的价值,求最大总价值 n<=100,m<=500
solution
看到这种有先决条件且先决条件只分 (0/1) 的背包, 第一时间想到的自然是 按先决条件连边,然后跑树形背包
但是,对于此题而言建边之后不一定是一棵树
而是很多个联通块
甚至还可能会有环!!!
对于有环的情况又该怎么处理呢?
若是有环,则显然环上的点都有其必要的先决条件,也就是说要把整个环一起放入背包,所以我们可以把这个环看做是一个整体
也就是用Tarjan缩点就好
而对于每个连通块,我们把他们都向 0(虚拟) 节点连边就好
好了,现在思路很明确了
先Tarjan缩点, 然后按缩完的点,以0号节点为根建树
然后直接跑树形DP
代码
#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define in inline
#define get getchar()
in int read()
{
int t=0; char ch=get;
while(ch<'0' || ch>'9') ch=get;
while(ch<='9' && ch>='0') t=t*10+ch-'0',ch=get;
return t;
}
const int _=501;
int n,m,w[_],h[_],d[_],v[_],dfn[_],tot,cnt,color,low[_],top,st[_],vis[_],co[_],ww[_],vv[_],dp[_][_];
struct edge{
int to,ne;
}e[_*_];
in void add(int x,int y)
{ e[++tot].to=y,e[tot].ne=h[x],h[x]=tot; }
in void Tarjan(int x)
{
low[x]=dfn[x]=++cnt;
st[++top]=x,vis[x]=1;
for(re int i=h[x];i;i=e[i].ne)
{
int y=e[i].to;
if(!dfn[y])
{
Tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(vis[y])
low[x]=min(low[x],dfn[y]);
}
if(low[x]==dfn[x])
{
++color;
while(st[top+1]!=x)
{
co[st[top]]=color;
ww[color]+=w[st[top]];
vv[color]+=v[st[top]];
vis[st[top--]]=0;
}
}
}
in void dfs(int x)
{
for(re int i=ww[x];i<=m;i++)
dp[x][i]=vv[x];
for(re int i=h[x];;i=e[i].ne)
{
if(i==0x3f3f3f3f)break;
int y=e[i].to;
dfs(y);
for(re int j=m-ww[x];j>=0;j--)
for(re int k=0;k<=j;k++)
dp[x][j+ww[x]]=max(dp[x][j+ww[x]],dp[y][k]+dp[x][j+ww[x]-k]);
}
}
int main()
{
n=read(),m=read();
for(re int i=1;i<=n;i++)
w[i]=read();
for(re int i=1;i<=n;i++)
v[i]=read();
for(re int i=1;i<=n;i++)
{
d[i]=read();
if(d[i])
add(d[i],i);
}
for(re int i=1;i<=n;i++)
if(!dfn[i]) Tarjan(i);
memset(h,0x3f,sizeof(h));
memset(vis,0,sizeof(vis));
memset(e,0,sizeof(e));
tot=0;
for(re int i=1;i<=n;i++)
if(co[d[i]]!=co[i])
{
add(co[d[i]],co[i]);
vis[co[i]]++;
}
for(re int i=1;i<=color;i++)
if(!vis[i])
add(0,i);
dfs(0);
cout<<dp[0][m]<<endl;
return 0;
}