【洛谷 2515】【HAOI 2010】【BZOJ 2427】软件安装【树形DP+Tarjan】

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


输入格式
第1行: N , M ( 0 ≤ N ≤ 100 , 0 ≤ M ≤ 500 ) N,M(0≤N≤100,0≤M≤500) N,M(0N100,0M500)

第2行: W 1 , W 2 , . . . W i , . . . , W n ( 0 ≤ W i ≤ M ) W_1,W_2, ... W_i, ..., W_n (0≤W i ≤M) W1,W2,...Wi,...,Wn(0WiM)

第3行: V 1 , V 2 , . . . , V i , . . . , V n ( 0 ≤ V i ​ ≤ 1000 ) V_1, V_2, ..., V_i, ..., V_n (0≤V i​≤1000) V1,V2,...,Vi,...,Vn(0Vi1000)

第4行: D 1 , D 2 , . . . , D i , . . . , D n ( 0 ≤ D i ​ ≤ N , D i = i ) D_1, D_2, ..., D_i, ..., D_n (0≤D_i​≤N,D_i =i) D1,D2,...,Di,...,Dn(0DiN,Di=i)


输出格式
一个整数,代表最大价值


在这里插入图片描述


解题思路
同样,这道题目类似是一个依赖的问题,是一道动态规划。但是它确实是树规么?

我们来想这样一组数据, 1 1 1依赖 2 2 2 2 2 2依赖 3 3 3, 3 3 3依赖 1 1 1。这样符合题目要求,但有形成了环,所以不是一棵树了。但是根据题目,这样特殊的情况,要么全要,要么全就不要。所以,事实上我们可以将这个环看成一个点再来动规,即缩点。如何判断是否是一个环呢,依照数据范围,我们想到了floyed(弗洛里德),这是在这种数据范围内性价比最高的方式。最后树规。于是一个比较清晰的步骤就出来了:判环,缩点,树规。

接下来是细节:
首先存树,可以用邻接矩阵。
f l o y e d floyed floyed:如果两点之间mapp[i][j]中有另一条路径相连,即 ( m a p p [ i ] [ k ] = 1 (mapp[i][k]=1 (mapp[i][k]=1 && m a p p [ k ] [ j ] = 1 ) mapp[k][j]=1) mapp[k][j]=1)( 1 1 1表示两点是通的);那么 m a p p [ i ] [ j ] mapp[i][j] mapp[i][j]也是通的且是环。

缩点:这个是最麻烦的,麻烦在于我们要把缩的点当成一个新点来判断,而且要判断某个点是否在某个环里。我们用染色法来判断,用所占的空间 w w w控制颜色的对应,有以下三种情况:

  • 点i所在的环之前没有判断过,是新环。那么,我们将这个新环放到数组最后,即新加一个点,然后让这两个点的空间标记为负值 t m p tmp tmp,且 t m p + tmp+ tmp+ n n nn nn(新点的下标)等于原来的点数 n n n,这样,我们就可以通过某个点的空间迅速找到他所在的新点。
  • 点j所在的环之前已经判断过了,是旧环(已合成新点),且 j j j是环的一部分。那么我们就把 j j j也加到这个新点里面,即体积,价值相加即可;
  • 点j依赖的点所在的环是旧环,但是 j j j不是环的一部分(例如 1 1 1依赖 2 , 2 2,2 22依赖 3 , 3 3,3 3,3依赖 1 1 1 4 4 4也依赖 1 1 1,那么, 4 4 4所在的是个环,但 4 4 4不属于环的一部分)。那么,把j的指向他依赖的点所在的环上 d [ j ] = n − w [ d [ j ] ] d[j]= n-w[d[j]] d[j]=nw[d[j]]

代码

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int n,m,nn,w[1000010],v[1000010],d[1000010],mapp[2010][2010],b[1000010],c[1000010],f[2010][2010],tmp;
void floyed(){//弗洛里德判断是否有环
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			for(int k=1;k<=n;k++)
				if(mapp[i][j]==1&&mapp[k][i]==1)
					mapp[k][j]=1;
}
void merge(){
	nn=n;
	for(int i=1;i<=nn;i++)
	{
		for(int j=1;j<=nn;j++)
		{
			if(mapp[i][j]==1&&mapp[j][i]==1&&i!=j&&w[i]>0&&w[j]>0)//如果是新环;
			{
				w[++nn]=w[i]+w[j];
				v[nn]=v[i]+v[j];
				tmp--;
				w[i]=tmp;
				w[j]=tmp;//tm+    nn(新点的下标)   等于原来的点数$n$
			}
			if(mapp[d[j]][j]==1&&mapp[j][d[j]]==1&&w[d[j]]<0&&w[j]>0)//如果j依赖的点被合并(是旧环),且j在环里
			{
				w[n-w[d[j]]]+=w[j];
				v[n-w[d[j]]]+=v[j];
				w[j]=w[d[j]];
			}
			if(w[d[j]]<0&&w[j]>0)//如果j依赖的点在环里,但是j不在环里
			{
				if((mapp[d[j]][j]==1&&mapp[j][d[j]]==0)||(mapp[d[j]][j]==0&&mapp[j][d[j]]==1))
					d[j]=n-w[d[j]];
			}
		}
	} 
}
int dfs(int dep,int bsy){
	if(f[dep][bsy]>0) return f[dep][bsy];
	if(dep==0||bsy<=0) return 0;
	f[b[dep]][bsy]=dfs(b[dep],bsy);
	f[dep][bsy]=f[b[dep]][bsy];
	//不取x
	int y=bsy-w[dep];
	for(int i=0;i<=y;i++)
	{
		f[c[dep]][y-i]=dfs(c[dep],y-i);
		f[b[dep]][i]=dfs(b[dep],i);
		f[dep][bsy]=max(f[dep][bsy],v[dep]+f[c[dep]][y-i]+f[b[dep]][i]);
	}
	//取x
	return f[dep][bsy];
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&w[i]);
	for(int i=1;i<=n;i++)
		scanf("%d",&v[i]);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&d[i]);
		mapp[d[i]][i]=1;
	}
	floyed();
	merge();
	for(int i=1;i<=nn;i++)
	{
		if(w[i]>0)
		{
			b[i]=c[d[i]];
			c[d[i]]=i;
		}
	}//反置
	printf("%d",dfs(c[0],m));
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值