题解 P1351 【联合权值】

先无良宣传一下博客 \(wwwwww\)
文章列表 - 地灵殿 - 洛谷博客


知识点: 树上问题 , \(dfs\)


  • 题目要求 :

    给定一棵树 ,
    求所有距离 \(2\) 的点对
    权值乘积的最大值权值乘积的和


  • 分析题意 :

    可以发现一些 比较浅显 的 性质 :
    满足题目条件的 点对 ,

    1. 在一条链上,且深度相差 \(2\) ,
      即一个点是另一个点的 \(2\) 级祖先 ,
      即 "父亲的父亲" ;
    2. 是同一个结点 的 子结点

  • 暴力开写 :

    通过上述性质 , 只需要进行一次 \(dfs\) ,
    就可以找到所有 点对

    1. 获得每个点的父亲
    2. 将一个点与其 父亲的父亲 ,
      即其二级祖先, 进行匹配
    3. 枚举一个点的出边时 ,
      将出边端点两两匹配 .

    求得匹配的点对的权值之积后 , 再进行处理即可 .

    直接按照上述过程 写出代码 ,
    取得了 \(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);
}
posted @ 2019-09-03 22:53  Luckyblock  阅读(169)  评论(0编辑  收藏  举报