[bzoj2427]P2515 [HAOI2010]软件安装(树上背包)

tarjan+树上背包

题目描述

现在我们的手头有 (N) 个软件,对于一个软件 (i),它要占用 (W_i) 的磁盘空间,它的价值为 (V_i)。我们希望从中选择一些软件安装到一台磁盘容量为 (M) 计算机上,使得这些软件的价值尽可能大(即 (V_i) 的和最大)。
但是现在有个问题:软件之间存在依赖关系,即软件 (i)只有在安装了软件 (j)(包括软件 (j) 的直接或间接依赖)的情况下才能正确工作(软件 (i) 依赖软件 (j) )。
幸运的是,一个软件最多依赖另外一个软件。如果一个软件不能正常工作,那么它能够发挥的作用为 (0)
我们现在知道了软件之间的依赖关系:软件 (i) 依赖软件 (D_i)
现在请你设计出一种方案,安装价值尽量大的软件。一个软件只能被安装一次,如果一个软件没有依赖则 (D_i=0),这时只要这个软件安装了,它就能正常工作。


把依赖关系想象成有向边,由被依赖的软件指向依赖它的软件
那么一个点能被选到的条件就是,它的祖先被选
然后每个点都只有一个入度,还是有向边,所以如果没有环的话,这就是一个树!直接树上背包就能行了

那么考虑用 tarjan 缩点,每个强连通分量中,每两个点都可以互相到达,而这个“互相到达”,放在这个题里就是互相直接或间接的依赖
所以强连通分量里的点,如果选就都选,不选就都不选,这点很好理解
那么缩点后的图就是树了吗?

是的,对于一个强连通分量来讲,很显然,想要满足每两个点互相到达的要求,每个点都必须有它所在的这个强连通分量中,其它的点连过来的边(废话),那么,此时它的入度已经为一了,就不会再有这个强连通分量以外的点向这个点连边了
所以,只有可能是这个强连通分量向外连边,而且是连到那种只有一个点的强连通分量中

此时要把图缩完点的情况,每个强连通分量作为节点,重新连边
那么同时构建一个虚拟节点,由它向没有入度的强联通分量连边,这个虚拟点的 (v,w) 均为 (0)
直接以这个虚拟节点为根做树上背包dp 就行了


简单说一下树上背包咋做
(f_{u,i}) 表示在 (u) 这个点,用 (i) 的硬盘限制,能获得的最大价值
初始是 (f_{u,i}=v_u,iin [w_u,m])
再分别枚举 (jin [0,m-w_u]) 表示对 所有 子树的限制,(kin[0,j]) 表示对 当前 子树的限制

[f_{u,j+w_u}=max(f_{u,j+w_u},f_{v,k}+f_{u,j+w_u-k}) ]

这个转移方程也就很好理解了

另外这个 (0-w_u<0) 是有可能的,然后我用 ~j 来判断它是不是等于 (-1) 来结束循环节爆炸了
我再也不用位运算了

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
	register int x=0;register int y=1;
	register char c=std::getchar();
	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
	return y?x:-x;
}
#define N 106
#define M 506
int fir[N],nex[N],to[N],tot;
int fir_[N],nex_[N],to_[N],tot_;
int dfn[N],low[N],dfscnt;
int scc[N],scccnt,indeg[N],sum_w[N],sum_v[N];
int stack[N],top;
int val[N],w[N];
int n,m;
int f[N][M];
inline void add(int u,int v){
	to[++tot]=v;
	nex[tot]=fir[u];fir[u]=tot;
}
inline void add_(int u,int v){
	to_[++tot_]=v;
	nex_[tot_]=fir_[u];fir_[u]=tot_;
}
void tarjan(int u){
	stack[top++]=u;low[u]=dfn[u]=++dfscnt;
	for(reg int v,i=fir[u];i;i=nex[i]){
		v=to[i];
		if(!dfn[v]){
			tarjan(v);
			low[u]=std::min(low[u],low[v]);
		}
		else if(!scc[v]) low[u]=std::min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){
		scccnt++;
		do{
			scc[stack[--top]]=scccnt;
			sum_w[scccnt]+=w[stack[top]];sum_v[scccnt]+=val[stack[top]];
		}while(stack[top]!=u);
	}
}
inline void rebuild(){
	for(reg int i=1;i<=n;i++){
		for(reg int j=fir[i];j;j=nex[j])if(scc[i]!=scc[to[j]])
			add_(scc[i],scc[to[j]]),indeg[scc[to[j]]]=1;
	}
	for(reg int i=1;i<=scccnt;i++)if(!indeg[i]) add_(scccnt+1,i);
}
void dfs(int u){
	for(reg int i=sum_w[u];i<=m;i++) f[u][i]=sum_v[u];
	reg int v,mm;
	for(reg int i=fir_[u];i;i=nex_[i]){
		v=to_[i];mm=m-sum_w[u];
		dfs(v);
		for(reg int j=mm;j>=0;j--){//j对 所有 子树的限制 
			for(reg int k=0;k<=j;k++)//k是对 当前 子树的限制 
				f[u][j+sum_w[u]]=std::max(f[u][j+sum_w[u]],f[v][k]+f[u][j+sum_w[u]-k]);
		}
	}
}
int main(){
	n=read();m=read();
	for(reg int i=1;i<=n;i++) w[i]=read();
	for(reg int i=1;i<=n;i++) val[i]=read();
	for(reg int i=1,x;i<=n;i++){
		x=read();
		if(x) add(x,i);
	}
	for(reg int i=1;i<=n;i++)if(!dfn[i]) tarjan(i);
	rebuild();
//		EN;
//		for(reg int i=1;i<=scccnt;i++) std::printf("%d : %d	",i,fir_[i]);EN;
//		std::puts("new : ");
//		for(reg int i=1;i<=scccnt;i++){
//			std::printf("%d : ",i);
//			for(reg int j=fir_[i];j;j=nex_[j]) std::printf("%d ",to_[j]);
//			EN;
//		}
//		for(reg int i=1;i<=scccnt;i++) std::printf("%d %d
",sum_v[i],sum_w[i]);
	dfs(scccnt+1);
	std::printf("%d",f[scccnt+1][m]);
	return 0;
}
原文地址:https://www.cnblogs.com/suxxsfe/p/12737529.html