zoukankan      html  css  js  c++  java
  • [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;
    }
    
  • 相关阅读:
    Linux下semaphore的使用 进程间互斥的一个好方法
    CURL编程:什么是NonASCII平台,如何取得页面的charset
    C++中的重载(Overload), 覆盖(Override)和隐藏(Hide)
    system调用虽然用了exec,但是fd, signal这些还是会保留父进程的,be careful
    addr2line,可以根据一个地址打印出对应的代码行
    POSIX Threads Programming 阅读笔记(来自劳伦斯利物浦实验室的文章)
    [Gammu]setlocale和bindtextdomain函数的用法
    常用编码
    用PL/SQL画直方图
    窗体的关闭
  • 原文地址:https://www.cnblogs.com/suxxsfe/p/12737529.html
Copyright © 2011-2022 走看看