题目类型:(tarjan)缩点+树形(dp)
传送门:>Here<
题意:给出N各节点,每个节点是一个软件,该软件有占用空间(W[i])和价值(V[i])。软件之间有依赖关系,如果想要运行(i),就必须安装(d[i])。问总空间不超过(M)时,运行的最大价值
解题思路
首先读题要仔细——安装和运行在这里是两个概念。安装相当于去选择一个软件集合,而在选择完所有的以后才能开始运行。我们可以选择建图,从(d[i])向(i)连边。通过读题不难发现所有节点的入度都不超过1。因此对于每一个连通块,要么是一个环,要么是一个(DAG)。而对于一个环,要么全部安装并运行,要么全部不安装(不可能安装一部分的环)。因此自然而然的,我们可以把环看做一个点,因此首先进行缩点。
缩点完了之后,整张图就变成了一个多个连通块的(DAG)。由于不存在环,每个连通块有且仅有存在入度为0的点。从一个虚拟根节点像所有入度为0的点连边,就形成了一个联通的(DAG)——由于入度不超过1,可以将其看做一棵树。
于是问题转化为树上的01背包,前提是要选子树的话必须选择该子树的根节点。
考虑树形背包的做法(这一点非常关键)。一般我们都令(dp[u][j])表示以(u)为根节点的子树中,总空间设定为(j)时的最大价值。然而这样的定义经常使人受到误导——这让人觉得很难转移。
实际上我们定义的原型应该是(dp[u][i][j])表示以(u)为根节点的子树中,前(i)棵子树中,总空间设定为(j)时的最大价值。因此我们有状态转移方程$$dp[u][i][j]=Max{dp[u][i-1][j],dp[u][i-1][j-k]+dp[v][all][k]}$$那为什么在普通的定义中没有(i)呢?因为(i)已经通过滚动数组优化了
这个方程的实质是爆扫——枚举当前子树分配到的空间,剩下的空间给之前的。但是这里有一个特殊的点,就是根节点必须选。这就意味着(dp[u][i-1][j-k])必须包含根节点,其实质也就是(j-k geq W[u])。这样每次转移的时候不至于让根节点消失了。
反思
读题是非常重要的。不误解题目意思是做出这道题的关键。当然考虑如何必须选根节点来推方程这方面好像还没什么经验,对于(dp)还是要多加练习啊。
Code
注意这是一个01背包。由于我们采用滚动数组优化,无论这是不是在树上,外层的(j)都是需要倒扫的。(当前一轮取决于上一轮比自己小的,正扫就会覆盖)
/*By DennyQi 2018*/
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 110;
const int MAXM = 510;
const int INF = 1061109567;
inline int Max(const int a, const int b){ return (a > b) ? a : b; }
inline int Min(const int a, const int b){ return (a < b) ? a : b; }
inline int read(){
int x = 0; int w = 1; register char c = getchar();
for(; c ^ '-' && (c < '0' || c > '9'); c = getchar());
if(c == '-') w = -1, c = getchar();
for(; c >= '0' && c <= '9'; c = getchar()) x = (x<<3) + (x<<1) + c - '0'; return x * w;
}
int N,M,dfs_clock,top;
int w[MAXN],v[MAXN],d[MAXN];
int _first[MAXN],_nxt[MAXM],_to[MAXM],_cnt;
int dfn[MAXN],low[MAXN],sta[MAXN],sccno[MAXN],scc_cnt;
int W[MAXN],V[MAXN],rd[MAXN];
int first[MAXN],nxt[MAXN],to[MAXN],cnt;
int dp[MAXN][50010];
inline void _add(int u, int v){
_to[++_cnt] = v, _nxt[_cnt] = _first[u], _first[u] = _cnt;
}
inline void add(int u, int v){
to[++cnt] = v, nxt[cnt] = first[u], first[u] = cnt;
}
void tarjan(int u){
int _v;
dfn[u] = low[u] = ++dfs_clock;
sta[++top] = u;
for(int i = _first[u]; i; i = _nxt[i]){
_v = _to[i];
if(!dfn[_v]){
tarjan(_v);
low[u] = Min(low[u], low[_v]);
}
else if(!sccno[_v]){
low[u] = Min(low[u], dfn[_v]);
}
}
if(low[u] == dfn[u]){
++scc_cnt;
while(1){
sccno[sta[top]] = scc_cnt;
W[scc_cnt] += w[sta[top]];
V[scc_cnt] += v[sta[top]];
--top;
if(sta[top+1] == u){
break;
}
}
}
}
void DP(int u){
for(int j = W[u]; j <= M; ++j){
dp[u][j] = V[u];
}
int v;
for(int i = first[u]; i; i = nxt[i]){
v = to[i];
DP(v);
for(int j = M; j >= W[u]; --j){
for(int k = 0; k <= j-W[u]; ++k){
dp[u][j] = Max(dp[u][j], dp[u][j-k] + dp[v][k]);
}
}
}
}
int main(){
// freopen(".in","r",stdin);
N = read(), M = read();
for(int i = 1; i <= N; ++i) w[i] = read();
for(int i = 1; i <= N; ++i) v[i] = read();
for(int i = 1; i <= N; ++i){
d[i] = read();
if(d[i]){
_add(d[i], i);
}
}
for(int i = 1; i <= N; ++i){
if(!dfn[i]){
tarjan(i);
}
}
for(int i = 1; i <= N; ++i){
if(sccno[i] != sccno[d[i]] && d[i]){
add(sccno[d[i]], sccno[i]);
++rd[sccno[i]];
}
}
for(int i = 1; i <= scc_cnt; ++i){
if(rd[i] == 0){
add(N+1, i);
}
}
DP(N+1);
printf("%d", dp[N+1][M]);
return 0;
}