[BZOJ2870] 最长道路tree

[BZOJ2870]最长道路tree


题意简述

给定一棵 \(n\) 个点的树,求树上一条链使得链的长度乘链上所有点中的最小权值所得的积最大。 其中链长度定义为链上点的个数。


算法一

我不会树上,但我会一条链!(此处默认 \(i\)\(i+1\) 相连)

用单调栈!对于每个点,维护比它大的最近的两个点,用距离乘自己权值即可。

预计得分 \(20\) 分。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int M=5e4+5;

int n,val[M],le[M],ri[M];
int s[M],top;

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&val[i]);
	for(int i=1,u,v;i<n;i++)
		scanf("%d%d",&u,&v);
	
	top=0,s[top]=0;
	for(int i=1;i<=n;i++)
	{
		while(top&&val[i]<=val[s[top]])top--;
		le[i]=s[top]+1;
		s[++top]=i;
	}
	top=0,s[top]=n+1;
	for(int i=n;i>0;i--)
	{
		while(top&&val[i]<=val[s[top]])top--;
		ri[i]=s[top]-1;
		s[++top]=i;
	}
	ll ans=0;
	for(int i=1;i<=n;i++)
		ans=max(ans,1ll*(ri[i]-le[i]+1)*val[i]);
	printf("%lld",ans);
	return 0;
}

算法二

我会树形 DP!

依然是枚举最小值,找最远的距离。对于每个最小值,都以每个点为根跑一遍,如果当前访问到的点权值 \(<w\) 就直接返回。否则就访问儿子节点,统计出最长和次长的链即可。

当然,如果一个点 \(y\) 已经在另一个点 \(x\) 的统计中被访问过了,那我们就不去统计该点 \(y\)。因为既然访问 \(x\) 能访问到 \(y\),那么它们必然处于同一个连通块中,且其中所有点的权值都 \(<w\)​。

时间复杂度为 \(O(n^2)\)​,预计得分 \(50\)​ 分。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int M=5e4+5;

int n,val[M],dp[M][2],mx;
vector<int> e[M];
bool vis[M];
ll ans;

void dfs(int u,int x)
{
	vis[u]=true;
	for(int i=0;i<e[u].size();i++)
	{
		int v=e[u][i];
		if(!vis[v]&&val[v]>=x)
		{
			dfs(v,x);
			if(dp[v][0]+1>dp[u][1])
			{
				dp[u][1]=dp[v][0]+1;
				if(dp[u][0]<dp[u][1])
					swap(dp[u][0],dp[u][1]);
			}
		}
	}
	mx=max(mx,dp[u][0]+dp[u][1]+1);
}
ll solve(ll x)
{
	memset(dp,0,sizeof(dp));mx=0;
	memset(vis,false,sizeof(vis));
	for(int i=1;i<=n;i++)
		if(!vis[i]&&val[i]>=x)
			dfs(i,x);
	return x*mx;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&val[i]);
	for(int i=1,u,v;i<n;i++)
	{
		scanf("%d%d",&u,&v);
		e[u].push_back(v);
		e[v].push_back(u);
	}
	for(int i=1;i<=n;i++)
		ans=max(ans,solve(val[i]));
	printf("%lld",ans);
	return 0;
}

算法三

我会并查集+LCA+树的直径!

仍然关注权值。我们把权值从大到小排序,按照顺序依次往图中加点,同时维护每个连通块中的最长链,乘以当前点权得到贡献。注意到:如果加了该点后,图中存在贡献更小答案,那么其贡献中的最小点权必然小于当前点权的。所以直接乘当前点权即可。

如何维护最长链(即树的直径)?

一个结论是:如果加一条边将两棵树连为一棵,那么形成的新树的直径的两端必然为原来两棵树直径共四个端点中的两个。

结论的证明与树的直径中两次 DFS 的证明类似,读者可以去参考一下。如 https://www.cnblogs.com/handsome-zyc/p/11237529.html

时间复杂度为 \(O(n\log_2 n)\),预计得分 \(100\) 分。

算法四

我会点分治!

我不会!所以我挂题解!https://www.cnblogs.com/hankeke/p/9853429.html

时间复杂度为 \(O(n\log_2^2 n)\),预计得分 \(100\) 分。

算法五

我会边分治!

我也不会!所以我也挂题解!https://blog.csdn.net/litble/article/details/80853633

时间复杂度为 \(O(n\log_2^2 n)\),预计得分 \(100\) 分。

posted @ 2021-07-26 17:56  cyl06  阅读(59)  评论(2编辑  收藏  举报