本文主要来记录一个没见过的树形背包 DP 写法
这题是个裸的 tarjan 缩点和树形背包 DP
显然循环依赖就缩成一点再进行 DP 即可
设 (f(i,j)) 为 dfs 序中第 i 个点及以后占用磁盘空间为 j 的连通块的最大价值
有 (f(i,j)=max {f(i+1,j-W[i])+V[i],f(i+size_{dfn_i},j)})
时间复杂度 (mathcal{O}(n imes m))
Code(C++):
#include<bits/stdc++.h>
#define forn(i,s,t) for(int i=(s);i<=(t);++i)
#define form(i,s,t) for(int i=(s);i>=(t);--i)
using namespace std;
const int N = 105,M = 505,INF = 1e9+7;
template<typename T>
inline void chkmax(T &A,T B) {A=A>B?A:B;}
struct List {
int dir,nxt;
}E[N],T[N];
int G[N],cnt,cntt,GT[N],dT[N];
inline void Add(int u,int v) {
E[++cnt].dir = v,E[cnt].nxt = G[u],G[u] = cnt;
}
inline void AddT(int u,int v) {
T[++cntt].dir = v,T[cntt].nxt = GT[u],GT[u] = cntt,dT[v] ++ ;
}
int n,m,w[N],v[N],d[N],dn[N],stk[N],h,ord,clr[N],col,W[N],V[N];
bool vis[N];
int tarjan(int u) {
int low = dn[u] = ++ord;
stk[++h] = u,vis[u] = 1;
for(int i=G[u];i;i=E[i].nxt) {
int v = E[i].dir;
if(!dn[v]) low = min(tarjan(v),low);
else if(vis[v]) low = min(low,dn[v]);
}
if(low == dn[u]) {
++col;
do clr[stk[h]]=col,W[col]+=w[stk[h]],V[col]+=v[stk[h]],vis[stk[h]]=0;
while(stk[h--]!=u);
}
return low;
}
int f[N][M],sz[N],rk[N];
void dfs(int u) {
sz[u] = 1,rk[++ord] = u;
for(int i=GT[u];i;i=T[i].nxt) {
int v = T[i].dir;
dfs(v),sz[u] += sz[v];
}
}
int main() {
scanf("%d%d",&n,&m);
forn(i,1,n) scanf("%d",&w[i]);
forn(i,1,n) scanf("%d",&v[i]);
forn(i,1,n) scanf("%d",&d[i]),d[i]?(Add(d[i],i),0):0;
forn(i,1,n) if(!dn[i]) tarjan(i);
forn(u,1,n) for(int i=G[u];i;i=E[i].nxt) {
int v = E[i].dir;
if(clr[u]!=clr[v]) AddT(clr[u],clr[v]);
}
forn(u,1,col) if(!dT[u]) AddT(0,u);
ord = 0,dfs(0);
form(i,ord,1) { int u = rk[i];
forn(j,0,m) {
chkmax(f[i][j],f[i+sz[u]][j]);
if(j>=W[u]) chkmax(f[i][j],f[i+1][j-W[u]]+V[u]);
}
}
printf("%d
",f[1][m]);
return 0;
}