分析:
单独计算每条链的贡献很耗时间,且不好计算,可以考虑分别计算每个点的贡献。
一些预处理
因为一个点的贡献与这个点在链中的(奇偶)位置有关,考虑树形DP:
令
f
u
,
0
,
f
u
,
1
f_{u,0},f_{u,1}
fu,0,fu,1表示分别表示在
u
u
u的子树中(包括
u
u
u)与
u
u
u的距离为偶数,奇数的点的数量。记
u
u
u的儿子的集合为
s
o
n
u
son_u
sonu,转移方程很明显为:
f
u
,
0
=
(
∑
v
∈
s
o
n
u
f
v
,
1
)
+
1
,
f
u
,
1
=
∑
v
∈
s
o
n
u
f
v
,
0
f_{u,0} = (\sum_{v \in son_u}f_{v,1}) + 1,\qquad f_{u,1} = \sum_{v \in son_u}f_{v,0}\qquad\qquad
fu,0=(v∈sonu∑fv,1)+1,fu,1=v∈sonu∑fv,0
开始计算
再考虑如何计算贡献,发现过任意一点 u u u的链在树中只有三种情况:
- 从 u u u的子树 — 到 u u u — 到 u u u的子树外
- 从 u u u的子树 — 到 u u u — 再回到 u u u的子树
- 从 u u u的子树外 — 到 u u u — 到 u u u的子树
(要注意题目中的链显然是有向的,所以1、3两种情况不能合并)
我们可以对三种情况分开讨论,其中要注意几种从自己开始或到自己结束的特殊情况。下面记最终答案为 a n s ans ans,点 u u u的子树大小为 s i z e u size_u sizeu,我们有:
对于情况1:
这里计算了三个特殊情况:自己到自己、子树到自己以及自己到子树外,有:
a
n
s
=
a
n
s
+
(
f
u
,
0
−
f
u
,
1
)
×
(
n
−
s
i
z
e
u
+
1
)
×
v
u
ans = ans + (f_{u,0} - f_{u,1})\times(n - size_u + 1) \times v_u\qquad\qquad
ans=ans+(fu,0−fu,1)×(n−sizeu+1)×vu
对于情况2:
略微复杂一点,没有计算任何特殊情况,注意子树到自己这个特殊情况在情况1已经算过了,所以计算最后一个节点时应当减一,为:
a
n
s
=
a
n
s
+
∑
v
∈
s
o
n
u
(
(
f
v
,
1
−
f
v
,
0
)
×
(
s
i
z
e
u
−
s
i
z
e
v
−
1
)
×
v
u
)
ans = ans + \sum_{v \in son_u} ((f_{v,1} - f_{v,0})\times(size_u - size_v - 1)\times v_u)\qquad
ans=ans+v∈sonu∑((fv,1−fv,0)×(sizeu−sizev−1)×vu)
对于情况3:
当前维护的信息好像无法计算?
考虑再维护一个数组
g
u
,
0
,
g
u
,
1
g_{u,0},g_{u,1}
gu,0,gu,1分别表示在点
u
u
u的子树外(不包括
u
u
u)与点
u
u
u的距离为偶数,奇数的点的数量,那么这个数组显然要从父节点转移,可以考虑搜索时记录每个点的深度,搜索完后按深度排序进行转移,这样就可以保证每个点被更新前父一定是被更新过的。令
f
a
u
fa_u
fau表示点
u
u
u的父节点,有转移方程:
g
u
,
0
=
g
f
a
u
,
1
+
f
f
a
u
,
1
−
f
u
,
0
,
g
u
,
1
=
g
f
a
u
,
0
+
f
f
a
u
,
0
−
f
u
,
1
g_{u,0} = g_{fa_u,1} + f_{fa_u,1} - f_{u,0},\qquad g_{u,1} = g_{fa_u,0} + f_{fa_u,0} - f_{u,1}\qquad
gu,0=gfau,1+ffau,1−fu,0,gu,1=gfau,0+ffau,0−fu,1
有了
g
g
g数组之后贡献就很好统计了,这里计算了一种特殊情况:子树外到自己,为:
a
n
s
=
a
n
s
+
(
g
u
,
0
−
g
u
,
1
)
×
s
i
z
e
u
×
v
u
ans = ans + (g_{u,0} - g_{u,1}) \times size_u \times v_u
ans=ans+(gu,0−gu,1)×sizeu×vu
这样情况3就算出来了
但你会发现上面还少了一种特殊情况:自己到子树。这个需要单独统计:
a
n
s
=
a
n
s
+
(
s
i
z
e
u
−
1
)
×
v
u
ans = ans + (size_u - 1) \times v_u
ans=ans+(sizeu−1)×vu
到这里三种情况就都统计完了,计算的时候注意取模即可
Code:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 2e5 + 50,mod = 1e9 + 7;
long long n,x,y,cnt,ans,a[maxn],v[maxn],fa[maxn],last[maxn],d[maxn],size[maxn],f[maxn][2],g[maxn][2];
//d为深度,a为按深度排序后的顺序,这样就不用写结构体
struct Edge{
int v,nxt;
}e[2 * maxn];
int read(){
int x = 0,f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f *= -1;
c = getchar();
}
while(c >= '0' && c <= '9') x = x * 10 + (c ^ 48),c = getchar();
return x * f;
}
inline void insert(int x,int y){
cnt ++,e[cnt].v = y,e[cnt].nxt = last[x],last[x] = cnt;
}
bool cmp(int x,int y){
return d[x] < d[y];//按深度排序
}
void dfs(int u,int father){
fa[u] = father,d[u] = d[fa[u]] + 1,size[u] = 1,f[u][0] = 1;
for(int i = last[u]; i; i = e[i].nxt){
int v = e[i].v;
if(v == father) continue;
dfs(v,u);
f[u][0] += f[v][1],f[u][1] += f[v][0],size[u] += size[v];
}
//把信息统计完后再计算贡献
for(int i = last[u]; i; i = e[i].nxt){
int t = e[i].v;
if(t == father) continue;
ans = (ans + (f[t][1] - f[t][0]) * (size[u] - size[t] - 1) % mod * v[u] % mod + mod) % mod;//情况2
}
ans = (ans + (size[u] - 1) * v[u] % mod + mod) % mod;//特殊情况,单独统计
ans = (ans + (f[u][0] - f[u][1]) * (n - size[u] + 1) % mod * v[u] % mod + mod) % mod;//情况1
}
int main(){
n = read();
for(int i = 1; i <= n; i ++) v[i] = read(),a[i] = i;
for(int i = 1; i < n; i ++){
x = read(),y = read();
insert(x,y),insert(y,x);
}
dfs(1,0);
sort(a + 1,a + 1 + n,cmp);
for(int i = 2; i <= n; i ++){
int u = a[i];
g[u][0] = g[fa[u]][1] + f[fa[u]][1] - f[u][0];
g[u][1] = g[fa[u]][0] + f[fa[u]][0] - f[u][1];
ans = (ans + (g[u][0] - g[u][1]) * size[u] % mod * v[u] % mod + mod) % mod;//情况3
}
cout << ans << endl;
return 0;
}