题意
给一棵 (n) 个点的树,点有点权,设 (max(x,y)) 表示 (x) 到 (y) 路径中所有点的点权最大值,(min(x,y)) 表示路径上点权的最小值,求:
[sumlimits_{i=1}^{n}sum
limits_{j=i+1}^{n}(max(i,j)-min(i,j))]
( exttt{Data Range:}1leq nleq 10^6)
题解
简单题。
将这个式子拆成两个,一个是所有的 (max),另一个是所有的 (min),然后分开求解。首先讨论 (max)。
如果是边权的话可以按照边权从小到大加边合并联通块。对于一条边 ((u,v,w)) 来说,它对答案的贡献是 (w imes sz_u imes sz_v),用并查集维护连通块的大小即可 (O(nlog n)) 完成。
但是因为这个题是点权就不行,需要转化成边权。一个常见的 trick(但是我今天第一次接触到)是将连接 (u) 和 (v) 的边的边权变成 (u,v) 两点点权的最大值,因为与一个数多次取 (max) 并不会改变最终结果。
对于 (min) 来说也是一样的,于是这个题就做完了。
代码
#include<bits/stdc++.h>
using namespace std;
typedef int ll;
typedef long long int li;
const ll MAXN=1e6+51;
struct Edge{
ll from,to,mn,mx;
inline bool operator <(const Edge &rhs)const
{
return mn>rhs.mn;
}
inline bool operator >(const Edge &rhs)const
{
return mx<rhs.mx;
}
};
Edge ed[MAXN];
ll n,from,to;
li mx,mn;
ll x[MAXN],ffa[MAXN],sz[MAXN];
inline ll read()
{
register ll num=0,neg=1;
register char ch=getchar();
while(!isdigit(ch)&&ch!='-')
{
ch=getchar();
}
if(ch=='-')
{
neg=-1;
ch=getchar();
}
while(isdigit(ch))
{
num=(num<<3)+(num<<1)+(ch-'0');
ch=getchar();
}
return num*neg;
}
inline ll find(ll x)
{
return x==ffa[x]?x:ffa[x]=find(ffa[x]);
}
inline void merge(ll x,ll y)
{
ll fx=find(x),fy=find(y);
fx!=fy?ffa[fy]=fx,sz[fx]+=sz[fy],sz[fy]=0:1;
}
int main()
{
n=read();
for(register int i=1;i<=n;i++)
{
x[i]=read(),ffa[i]=i,sz[i]=1;
}
for(register int i=1;i<n;i++)
{
from=read(),to=read();
ed[i]=(Edge){from,to,min(x[from],x[to]),max(x[from],x[to])};
}
sort(ed+1,ed+n);
for(register int i=1;i<n;i++)
{
mn+=(li)sz[find(ed[i].from)]*sz[find(ed[i].to)]*ed[i].mn;
merge(ed[i].from,ed[i].to);
}
sort(ed+1,ed+n,greater<Edge>());
for(register int i=1;i<=n;i++)
{
ffa[i]=i,sz[i]=1;
}
for(register int i=1;i<n;i++)
{
mx+=(li)sz[find(ed[i].from)]*sz[find(ed[i].to)]*ed[i].mx;
merge(ed[i].from,ed[i].to);
}
printf("%lld
",mx-mn);
}