[NOIP2014-提高组] 联合权值 - 题解
-
前言
未经允许,请勿转载。
-
题目
无向连通图 \(G\) 有 \(n\) 个点,\(n-1\) 条边。点从 \(1\) 到 \(n\) 依次编号,编号为 \(i\) 的点的权值为 \(W_i\) ,每条边的长度均为 \(1\)。图上两点 \((u,v)\) 的距离定义为 \(u\) 点到 \(v\) 点的最短距离。对于图 \(G\) 上的点对 \((u,v)\) ,若它们的距离为 \(2\) ,则它们之间会产生 \(W_v \times W_u\) 的联合权值。
请问图 \(G\) 上所有可产生联合权值的有序点对中,联合权值最大的是多少?所有联合权值之和是多少?
-
分析题目
-
无向连通图 \(G\) 有 \(n\) 个点,\(n-1\) 条边。
很明显,给出的是一个无根树
-
由于所有联合权值之和可能很大,输出它时要对\(10007\)取余。
易错点,最大值不用取膜,和才要取膜。
-
-
分析
因为他是一个无根树,所以不如先给他确认\(1\)为其根节点。
因为他是一个树,那么对于每个点只要考虑\(3\)种情况:父节点的父节点(爷爷),他的子节点的子节点,和他有同一个父亲的任意一点(孙子们)
然后又可以发现,可以忽略每个点的子节点的子节点的计算 ,相应的,只要把每次算父节点的父节点时的值算上2遍就可以了。
这里稍微解释一下:(理解的人自动跳过)
设 \(x\) 的父节点的父节点是 \(y\) 。
在算 \(x\) 的情况时,要以第一种情况(父节点的父节点) 求一次 \(w_x*w_y\)。算 \(y\) 的情况时,也要以第二种情况(子节点的子节点) 算一遍 \(w_y * w_x\) 。
所以可以直接忽略第二种情况,把第一种情况的结果乘上 \(2\)。
好了继续。
这么一分析完,就可以直接跑 \(dfs\) 了。
对于每个点,第一种情况直接算(直接乘法);第二种情况,直接枚举和自己有同一个父亲的所有点,不断更新答案即可。
-
一点细小的优化(我当时以为不加能过)
直接\(dfs\)直接TLE,还需要一点点优化。
看刚刚最后的结论,发现每次枚举的点权是可以预处理的。
设 \(sum[x]\) 表示点 \(x\) 所有子节点点权和。
而最大值,对于一个点 \(x\) ,也求出他的子节点的最大值和次大值。(因为下一层的最大值的点不能和自己乘)
然后就可以了。
如果还有些不理解,可以看看代码。
- 代码
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const int dalao=10007;
struct edge{
int v,nx;
}p[400005];
int n,ne,f[200005];
int sum[200005],father[200005],w[200005];
int data[200005][2],gmax[200005][2];//data记录编号 gmax记录具体值 0最大值 1次大值
int maxn,ans;
void read(int u,int v)
{ p[++ne].v=v;
p[ne].nx=f[u];
f[u]=ne;
}
void getfather(int x,int fa)
{ father[x]=fa;
for(int i=f[x];i;i=p[i].nx)
{ if(p[i].v==fa)continue;
getfather(p[i].v,x);
}
}
int main()
{ scanf("%d",&n);
for(int i=1;i<n;i++)
{ int u,v;
scanf("%d%d",&u,&v);
read(u,v);
read(v,u);
}
for(int i=1;i<=n;i++)
scanf("%d",&w[i]);
sum[0]=w[1];
getfather(1,0);//确认每个点的父节点
for(int i=1;i<=n;i++)//预处理
for(int j=f[i];j;j=p[j].nx)
{ if(p[j].v==father[i])continue;
sum[i]=(sum[i]+w[p[j].v])%dalao;//预处理和
//预处理最大值和次大值
if(w[p[j].v]>gmax[i][0]){gmax[i][0]=w[p[j].v];data[i][0]=p[j].v;}
else if(w[p[j].v]>gmax[i][1]){gmax[i][1]=w[p[j].v];data[i][1]=p[j].v;}
}
for(int x=1;x<=n;x++)
{ if(father[father[x]])//存在父节点的父节点
ans=(ans+2*w[x]*w[father[father[x]]])%dalao;
if(father[father[x]])
maxn=max(maxn,w[x]*w[father[father[x]]]);
ans=(ans+w[x]*(sum[father[x]]-w[x]))%dalao;
if(data[father[x]][0]!=x)maxn=max(maxn,w[x]*gmax[father[x]][0]);
else maxn=max(maxn,w[x]*gmax[father[x]][1]);
}
printf("%d %d\n",maxn,ans);
return 0;
}