【luogu2515】【HAOI2010】【BZOJ2427】【Tarjan】【树形DP】软件安装

57 篇文章 0 订阅
3 篇文章 0 订阅

传送门

题目描述

现在我们的手头有 N N N个软件,对于一个软件i,它要占用 W i W_i Wi的磁盘空间,它的价值为 V i ​ V_i​ Vi。我们希望从中选择一些软件安装到一台磁盘容量为 M M M计算机上,使得这些软件的价值尽可能大(即 V i V_i Vi的和最大)。

但是现在有个问题:软件之间存在依赖关系,即软件 i i i只有在安装了软件 j j j(包括软件j的直接或间接依赖)的情况下才能正确工作(软件 i i i依赖软件 j j j)。幸运的是,一个软件最多依赖另外一个软件。如果一个软件不能正常工作,那么它能够发挥的作用为 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\leq N\leq 100, 0\leq M\leq 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\leq W_i\leq 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\leq V_i\leq 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\leq D_i\leq N, D_i≠i) D1,D2,...,Di,...,Dn(0DiN,Di=i)

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

输入输出样例
输入 #1

3 10
5 5 6
2 3 4
0 1 1

输出 #1

5

解题思路

本题难点
缩点

先进行缩点,然后做DP

缩点(个人理解)
一个有依赖的图中
有一些环,环中点必须全选,只要有一个点不选那么所有点都会失效
在这里插入图片描述
比如:1依赖2,2依赖3,3依赖1
不管哪个点不选,整个环都废了
所以可以把环看做一个整体,看做一个点

Tarjan缩点(个人理解)
找出所有的强连通分量(一个环),每个强连通分量缩为一个点,再重新建树

DP
这题DP蛮简单的,就普通的背包

推荐一下某博主的博客,非常详细,就是在那看懂的Tarjan


Code
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct DT{
	int to,next;
}sa[3020],a[3020];
int n,m,com,sw[3020],w[3020],sv[3020],v[3020],sd[3020],co[3020],fam[3020],vis[3020];
int snum,shead[3020],num,head[3020],now,top,adfs[3020],afa[3020],hep[3020],dp[3020][5020];//变量看起来好可怕TAT
void sadd(int x,int y){
	sa[snum].to=y,sa[snum].next=shead[x],shead[x]=snum++;
}
void add(int x,int y){
	a[num].to=y,a[num].next=head[x],head[x]=num++;
}
void tarjan(int x){
	now++;
	adfs[x]=afa[x]=now;//adfs记录x被发现时间,afa记录x的最早父亲
	hep[++top]=x;vis[x]=1;//hep是栈,vis记录x有没有进栈
	for(int i=shead[x];i!=-1;i=sa[i].next){
		int y=sa[i].to;
		if(!adfs[y]){//从没访问过的
			tarjan(y);
			afa[x]=min(afa[x],afa[y]);
		 }else if(vis[y])afa[x]=min(afa[x],adfs[y]);
	} 
	if(adfs[x]==afa[x]){//证明已经触底并回溯到根了
		++com;//染色(每个强连通分量都被标为一个颜色)
        vis[x]=0; 
		while(hep[top+1]!=x){//退栈
			co[hep[top]]=com;//染色
			vis[hep[top--]]=0;
		}
	}
}
void dfs(int x){
	for(int i=w[x];i<=m;i++)
	    dp[x][i]=v[x]; //初始化
	for(int k=head[x];k!=-1;k=a[k].next){
		int y=a[k].to;
		dfs(y);
		for(int i=m-w[x];i>=0;i--)
		    for(int j=0;j<=i;j++)
		        dp[x][i+w[x]]=max(dp[x][i+w[x]],dp[x][i+w[x]-j]+dp[y][j]);//背包(●—●)
	}
}
int main(){
	memset(shead,-1,sizeof(shead));
	memset(head,-1,sizeof(head));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&sw[i]);
	for(int i=1;i<=n;i++)
	    scanf("%d",&sv[i]);
	for(int i=1;i<=n;i++){
	    scanf("%d",&sd[i]);
	    if(sd[i])
		   sadd(sd[i],i);
	} 
	for(int i=1;i<=n;i++)
	    if(!adfs[i])
	       tarjan(i);
	for(int i=1;i<=n;i++){
		v[co[i]]+=sv[i],w[co[i]]+=sw[i];//缩点,把每个强联通分量的价值和费用合并完成缩点
		if(co[i]!=co[sd[i]]&&sd[i]){
			add(co[sd[i]],co[i]);
			fam[co[i]]++;
		}
	}
	int s=com+1;//找一个图外的点作为一个大的根,把森林连成一棵树
	for(int i=1;i<=com;i++)
	    if(!fam[i])
	       add(s,i);//与根建边
	v[s]=w[s]=0;
	dfs(s); 
	printf("%d",dp[s][m+w[s]]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值