题解 P1351 【联合权值】
先无良宣传一下博客 \(wwwwww\)
文章列表 - 地灵殿 - 洛谷博客
知识点: 树上问题 , \(dfs\)
-
原题面: P1351 联合权值
-
题目要求 :
给定一棵树 ,
求所有距离 \(2\) 的点对
权值乘积的最大值 和 权值乘积的和
-
分析题意 :
可以发现一些 比较浅显 的 性质 :
满足题目条件的 点对 ,- 在一条链上,且深度相差 \(2\) ,
即一个点是另一个点的 \(2\) 级祖先 ,
即 "父亲的父亲" ; - 是同一个结点 的 子结点
- 在一条链上,且深度相差 \(2\) ,
-
暴力开写 :
通过上述性质 , 只需要进行一次 \(dfs\) ,
就可以找到所有 点对- 获得每个点的父亲
- 将一个点与其 父亲的父亲 ,
即其二级祖先, 进行匹配 - 枚举一个点的出边时 ,
将出边端点两两匹配 .
求得匹配的点对的权值之积后 , 再进行处理即可 .
直接按照上述过程 写出代码 ,
取得了 \(70\) 分的好成绩 , \(T\) 掉了 \(3\) 个点 .
-
考虑优化 :
发现 \(dfs\) 中,枚举出边端点 , 并进行两两匹配,
总复杂度是 \(O(n^2)\) 级别的 , 必然会被卡掉 .
但是 两两匹配过程 又不可缺少
考虑优化 这 必要的一步.考虑 某结点的所有子结点 ,
可以发现 :
\(\text{其 最大权值积} = \text{最大子结点权值} \times \text{次大子结点权值}\)
\(\text{其 权值和} = \sum(\text{每个子结点权值} \times \text{其他各子结点的权值和})\)考虑预处理出 每个结点的 最大子结点权值 , 次大子节点权值 , 子结点权值和
只需要一次 \(O(n)\) 的 \(dfs\) 即可通过上述优化 , 已经可以 \(A\) 掉本题了
-
继续优化
(其实是因为不会预处理次大值)
真的有必要处理出 次大子节点权值吗?
发现根本不需要 , 只需要处理出最大子节点权值即可当枚举到次大子节点时 , 将其与 最大子节点权值相乘,
即可得到 \(u\) 的子节点的最大权值积同时注意判重 , 不能使 \(\ \text{最大点}\times\text{最大点}\) ,
可以通过记录 最大点 的编号来实现.
-
一些小技巧:
由于没有边权值 ,
只需要知道每一条边的起点终点即可
所以 可以使用 \(vector\) 存边 .
易于枚举匹配 , 还节省空间
附 \(AC\) 代码 :
#include<cstdio>
#include<ctype.h>
#include<vector>
#define max(a,b) ((a)>(b)?(a):(b))
const int MARX = 2e5+10;
const int mod = 1e4+7;
//=============================================================
std::vector <int> e[MARX];
int ans1,ans2,n,w[MARX];
int num,head[MARX],father[MARX];
int maxw[MARX],sumw[MARX],maxv[MARX]; //分存:i的直接子节点 的 最大权值,权值和,最大权值点的编号
//=============================================================
inline int read()
{
int s=1, w=0; char ch=getchar();
for(; !isdigit(ch);ch=getchar()) if(ch=='-') s =-1;
for(; isdigit(ch);ch=getchar()) w = w*10+ch-'0';
return s*w;
}
void dfs1(int u,int fa)//预处理出 u的子节点 的最大权值 和 权值和
{
for(int i=0;i<e[u].size();i++)//枚举每一个出点
if(e[u][i]!=fa)
{
if(w[e[u][i]] >maxw[u]) maxw[u]=w[e[u][i]] , maxv[u]=e[u][i];//预处理最大权值
sumw[u]=(sumw[u]+w[e[u][i]])%mod;//预处理权值和
dfs1(e[u][i],u);//递归进行
}
}
void dfs2(int u,int fa)
{
father[u]=fa;//记录父亲
if(father[father[u]]) //如果存在 二级祖先
ans1=max(ans1,w[u]*w[father[father[u]]]),//进行配对
ans2=( ans2 + 2*w[u]*w[father[father[u]]] )%mod;
for(int i=0;i<e[u].size();i++)//枚举直接子结点
if(e[u][i]!=fa)
{
if(e[u][i]!=maxv[u]) ans1=max(ans1,w[e[u][i]]*maxw[u]);//如果当前点不为最大点
ans2=( ans2 + w[e[u][i]]*(sumw[u]-w[e[u][i]]) )%mod;//增加权值和
dfs2(e[u][i],u);
}
}
//=============================================================
signed main()
{
n=read();
for(int i=1;i<n;i++) //vector 建图
{
int u=read(),v=read();
e[u].push_back(v);
e[v].push_back(u);
}
for(int i=1;i<=n;i++) w[i]=read();
dfs1(1,0);//预处理
dfs2(1,0);
printf("%d %d",ans1,ans2%mod);
}