【CF856D】Masha and Cactus-树形DP+LCA+树状数组

测试地址:Masha and Cactus
题目大意: 给定一棵树,再给定 m m m条边,每条边有权值,从里面选出一些边加入树中,使得形成的图是仙人掌,即每个点至多处在一个环中的图,并使得加入的边的权值和最大,求出这个最大值。
做法: 本题需要用到树形DP+LCA+树状数组。
首先转化一下问题。不难想到,添加一条边会使得树上的一条路径上的点被一个环覆盖,那么要使一个点至多在一个环中,也就是要求添加的边所对应的路径不相交。那么问题就转化为,从 m m m条路径中选出若干不相交的路径,使得它们的权值和最大。
不难想到用树形DP选择此题。令 f ( v ) f(v) f(v)为以 v v v为根子树的答案,那么 v v v要么不被覆盖,这种情况答案就是它所有儿子的 f f f值之和,要么 v v v就被某条路径覆盖,而因为我们要算的是以 v v v为根子树的答案,所以我们只需考虑LCA为 v v v的路径即可。这样的话每条路径会在其两个端点的LCA处被处理,直接在DP前预处理出来即可。在选择了某一条路径后,要使得答案最大,就是要求在子树中把该路径所有点删掉,剩下的所有子树都达到最大,也就是求子树中和这条路径直接相邻的点的 f f f值之和。
怎么求这个东西呢?画一画图可以观察到,对路径上每个点,我们把它所有儿子的 f f f值之和累计,那么最后多算出来的贡献就是除了点 v v v,其余在路径上的点的 f f f值之和,我们把这个多算的部分减去就行了。再稍微调整一下式子,令 s ( v ) s(v) s(v)为点 v v v所有儿子的 f f f值之和,减去点 v v v f f f值,并令当前路径为 ( x , y ) (x,y) (x,y),那么答案就可以表示为,点 v v v的所有儿子 f f f值之和,加上 v v v x x x路径上所有点的 s s s值之和(不包括点 v v v),再加上 v v v y y y路径上所有点的 s s s值之和(不包括点 v v v)。这样我们其实就是求这样一个问题:每个点有点权,询问这一点到某点 v v v的路径上的点权和。因为DP的过程就是自下而上的,因此每当我们处理完一个点 v v v,它的点权就会为它子树中所有点的贡献增加 s ( v ) s(v) s(v),在DFS序上就是一个区间加、单点询问的问题,显然差分后用树状数组处理即可,那么我们就解决了这一题,时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
int n,m,fa[200010][20]={0},dep[200010],first[200010]={0},tot=0;
int in[200010],out[200010],tim=0,a[200010],b[200010],val[200010];
int sum[200010]={0},f[200010];
vector<int> p[200010];
struct edge
{
	int v,next;
}e[200010];

void insert(int a,int b)
{
	e[++tot].v=b;
	e[tot].next=first[a];
	first[a]=tot;
}

void dfs(int v)
{
	in[v]=++tim;
	for(int i=first[v];i;i=e[i].next)
	{
		dep[e[i].v]=dep[v]+1;
		dfs(e[i].v);
	}
	out[v]=tim;
}

int lca(int a,int b)
{
	if (dep[a]<dep[b]) swap(a,b);
	for(int i=18;i>=0;i--)
		if (dep[fa[a][i]]>=dep[b])
			a=fa[a][i];
	if (a==b) return a;
	for(int i=18;i>=0;i--)
		if (fa[a][i]!=fa[b][i])
			a=fa[a][i],b=fa[b][i];
	return fa[a][0];
}

int lowbit(int x)
{
	return x&(-x);
}

void add(int x,int c)
{
	for(int i=x;i<=n;i+=lowbit(i))
		sum[i]+=c;
}

int calc_sum(int x)
{
	int ans=0;
	for(int i=x;i;i-=lowbit(i))
		ans+=sum[i];
	return ans;
}

void dp(int v)
{
	int downsum=0;
	for(int i=first[v];i;i=e[i].next)
	{
		dp(e[i].v);
		downsum+=f[e[i].v];
	}
	f[v]=downsum;
	for(int i=0;i<p[v].size();i++)
	{
		int x=a[p[v][i]],y=b[p[v][i]],V=val[p[v][i]];
		f[v]=max(f[v],downsum+calc_sum(in[x])+calc_sum(in[y])+V);
	}
	add(in[v],downsum-f[v]);
	add(out[v]+1,f[v]-downsum);
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=2;i<=n;i++)
	{
		scanf("%d",&fa[i][0]);
		insert(fa[i][0],i);
	}
	dep[0]=-1,dep[1]=0;
	dfs(1);
	for(int i=1;i<=18;i++)
		for(int j=1;j<=n;j++)
			fa[j][i]=fa[fa[j][i-1]][i-1];
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&a[i],&b[i],&val[i]);
		p[lca(a[i],b[i])].push_back(i);
	}
	
	dp(1);
	printf("%d",f[1]);
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值