YBTOJ 软件安装

题面:洛谷传送门
题目算法要素:tarjan+树形dp

题目分析:

一、总体概括

可以发现一个环中的点必须同时被选择,因此很容易能想到要tarjan缩点。
缩点后形成一张DAG,由于题目的条件,d[i]=0表示一个软件没有另一个软件为前提,因此有一个超级源点0。可以考虑从0点开始,跑一遍树形dp(树上背包),即可得出答案。

二(细节一):为什么不能用拓扑dp求解:

如果跑一遍拓扑dp,可以得出以每一个点为终点的最大权链,但是各个链中显然有重合的可能性,因此无法直接通过再跑一遍01背包的方式将以每个点为终点的最大权链合成整张DAG的最优解。
因此拓扑dp显然不行。

这道题也告诉我们缩点不是一定要用拓扑dp求解的。
三(细节二):如何在树形dp中强制选择当前点

我们使用dp式:f[u][j]表示在以u根的子树中,耗费j的存储空间,能得到的软件的最大总价值。
但是别忘了只有选择了根节点,才能选择子树中的点。
因此我们加上一条限制条件:对于f[u][j],强制要求选择点u。

这其实是一类问题:如何在树形dp中强制选择一棵子树的根节点

肯定要考虑枚举j和子树费用k的过程(修改转移式)
如果没有这个条件,我们显然会把状态转移写成这个样子

令u的一个子节点为v
for(int j=sumw[u];j<=m;++j) f[u][j]=sumv[u]; 
for(int j=m;j>=0;--j)
{
	for(int k=0;k<=j;++k)
		f[u][j]=max(f[u][j],f[u][j-k]+f[v][k]);
}

加上这个限制条件,就相当于:给选择根节点留下足够的存储空间

for(int j=sumw[u];j<=m;++j) f[u][j]=sumv[u]; 
for(int j=m-sumw[u];j>=0;--j)
{
	for(int k=0;k<=j;++k)
		f[u][j+sumw[u]]=max(f[u][j+sumw[u]],f[u][j+sumw[u]-k]+f[v][k]);
}

由于每一个位置都一定给u留够了空间,且每次计算空间都加上sumw[u],另外,最开始的初始化是给每个未选择的空间都赋上了sumv[u],因此sumv[u]一定会被选择。

或者还有一种写法
就是在子树不包括u的部分选择的空间一定比在子树包括u的部分选择的空间少一个sumw[u],也就是一定把u选择上了。

void dp(int now)
{
	for(int i=sumw[now];i<=m;++i) f[now][i]=sumv[now];
	for(int i=head2[now];~i;i=e2[i].nxt)
	{
		int v=e2[i].v;
		dp(v);
		for(int j=m;j>=sumw[now];--j)
		{
			for(int k=0;k<=j-sumw[now];++k)
			{
				f[now][j]=max(f[now][j],f[now][j-k]+f[v][k]);
			}
		}
	}
}

Code

#include<bits/stdc++.h>
using namespace std;
const int maxn=550;
int n,m,w[maxn],v[maxn],d[maxn],tot,col[maxn];
int dfn[maxn],low[maxn],Time,sumw[maxn],sumv[maxn];
int ecnt=-1,ecnt2=-1,head[maxn],head2[maxn];
int s[maxn],top,indu[maxn],f[105][505];
struct mint
{
	int nxt,u,v;	
}e[maxn],e2[maxn];
inline void addline(int u,int v)
{
	e[++ecnt].nxt=head[u];
	e[ecnt].u=u;
	e[ecnt].v=v;
	head[u]=ecnt;
}
inline void addline2(int u,int v)
{
	e2[++ecnt2].nxt=head2[u];
	e2[ecnt2].u=u;
	e2[ecnt2].v=v;
	head2[u]=ecnt2;	
}
void tarjan(int now)
{
	dfn[now]=low[now]=++Time;
	s[++top]=now;
	for(int i=head[now];~i;i=e[i].nxt)
	{
		int v=e[i].v;
		if(!dfn[v])
		{
			tarjan(v);
			low[now]=min(low[now],low[v]);	
		}
		else if(!col[v]) low[now]=min(low[now],dfn[v]);
	}
	if(dfn[now]==low[now])
	{
		tot++;
		int cur;
		do
		{
			cur=s[top--];
			col[cur]=tot;
			sumw[tot]+=w[cur];
			sumv[tot]+=v[cur];
		}
		while(cur!=now);
	}
}
void dp(int now)
{
	for(int i=sumw[now];i<=m;++i) f[now][i]=sumv[now];
	for(int i=head2[now];~i;i=e2[i].nxt)
	{
		int v=e2[i].v;
		dp(v);
		for(int j=m-sumw[now];j>=0;--j)
		{
			for(int k=0;k<=j;++k)
			{
				f[now][j+sumw[now]]=max(f[now][j+sumw[now]],f[now][j+sumw[now]-k]+f[v][k]);
			}
		}
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	memset(head,-1,sizeof(head));
	memset(head2,-1,sizeof(head2));
	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]);
		addline(d[i],i);
	}
	for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i);
	for(int i=0;i<m;++i)
	{
		if(col[e[i].v] != col[e[i].u])
		{
			addline2(col[e[i].u],col[e[i].v]); 
			indu[col[e[i].v]]++;
		}
	}
	for(int i=1;i<=tot;++i) if(!indu[i]) addline2(0,i);
	dp(0);
	printf("%d",f[0][m]);
	return 0;	
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值