送你一颗圣诞树 \operatorname{送你一颗圣诞树} 送你一颗圣诞树
题目链接: SSL比赛 1484 \operatorname{SSL比赛\ 1484} SSL比赛 1484
题目
再过三个多月就是圣诞节了,小 R 想送小 Y 一棵圣诞树作为节日礼物。因为他想让这棵圣诞树越大越好,所以当然是买不到能够让他满意的树的,因此他打算自己把这棵树拼出来。
现在,小 R 开始画这棵树的设计图纸了。因为这棵树实在太大,所以他采用了一种比较方便的方法。首先他定义了
m
+
1
m+ 1
m+1 棵树
T
0
T_0
T0 到
T
m
T_m
Tm 。最开始他只画好了
T
0
T_0
T0 的图纸:就只有一个点,编号为
0
0
0 。
接着,对于每一棵树
T
i
T_i
Ti ,他在第
T
a
i
T_{a_i}
Tai 棵树的第
c
i
c_i
ci 个点和第
T
b
i
T_{b_i}
Tbi 棵树的第
d
i
d_i
di 个点之间连上了一条长度为
l
i
l_i
li 的边。在
T
i
T_i
Ti 中,他保持
T
a
i
T_{a_i}
Tai 中的所有节点编号不变,然后如果
T
a
i
T_{a_i}
Tai 中有
s
s
s 个节点,他会把
T
b
i
T_{b_i}
Tbi 中的所有节点的编号加上
s
s
s 。
终于,他画好了所有的树。现在他定义一颗大小为
n
n
n 的树的美观度为
∑
i
=
0
n
−
1
∑
j
=
i
+
1
n
−
1
d
(
i
,
j
)
\sum_{i=0}^{n-1} \sum_{j=i+1}^{n-1}d(i,j)
∑i=0n−1∑j=i+1n−1d(i,j) ,其中
d
(
i
,
j
)
d(i,j)
d(i,j) 为这棵树中
i
i
i 到
j
j
j 的最短距离。
为了方便小 R 选择等究竟拼哪一棵树,你可以分别告诉他
T
1
T_1
T1 到
T
m
T_m
Tm 的美观度吗?答案可能很大,请对
1
0
9
+
7
10^9 + 7
109+7 取模后输出。
输入
第一行输入一个正整数 T T T 表示数据组数。每组数据的第一行是一个整数 m m m ,接下来 m m m 行每行五个整数 a i , b i , c i , d i , l i a_i, b_i, c_i, d_i, l_i ai,bi,ci,di,li ,保证 0 < = a i , b i < i 0 <= a_i, b_i < i 0<=ai,bi<i , 0 < = l i < = 1 0 9 0<= l_i<= 10^9 0<=li<=109 , c i , d i c_i, d_i ci,di 存在。
输出
对于每组询问输出 m m m 行。第 i i i 行输出 T i T_i Ti 的权值
样例输入
1
2
0 0 0 0 2
1 1 0 0 4
样例输出
2
28
数据范围
对于
30
%
30\%
30% 的数据,
m
<
=
8
m <= 8
m<=8
对于
60
%
60\%
60% 的数据,
m
<
=
16
m <= 16
m<=16
对于
100
%
100\%
100% 的数据,
1
<
=
m
<
=
60
1 <= m<= 60
1<=m<=60 ,
T
<
=
100
T<= 100
T<=100
思路
这道题是一道 dp 。
我们让
a
n
s
[
i
]
ans[i]
ans[i] 为第
i
i
i 个答案,
g
e
t
a
l
l
[
i
]
[
j
]
getall[i][j]
getall[i][j] 表示第
i
i
i 棵树中所有点到
j
j
j 点的距离,
s
i
z
e
[
i
]
size[i]
size[i] 为第
i
i
i 棵树的大小,那我们可以推出这个式子:
a
n
s
[
i
]
=
a
n
s
[
a
[
i
]
]
+
a
n
s
[
b
[
i
]
]
+
g
e
t
a
l
l
[
a
[
i
]
]
[
c
[
i
]
]
∗
s
i
z
e
[
b
[
i
]
]
+
g
e
t
a
l
l
[
b
[
i
]
]
[
d
[
i
]
]
∗
s
i
z
e
[
a
[
i
]
]
+
s
i
z
e
[
a
[
i
]
]
∗
s
i
z
e
[
b
[
i
]
]
∗
l
[
i
]
ans[i]=ans[a[i]]+ans[b[i]] + getall[a[i]][c[i]]*size[b[i]] + getall[b[i]][d[i]]*size[a[i]]+ size[a[i]]*size[b[i]] * l[i]
ans[i]=ans[a[i]]+ans[b[i]]+getall[a[i]][c[i]]∗size[b[i]]+getall[b[i]][d[i]]∗size[a[i]]+size[a[i]]∗size[b[i]]∗l[i]
s i z e [ i ] size[i] size[i] 很好求,就设 s i z e [ 0 ] = 1 size[0] = 1 size[0]=1 ,然后每次读入加一下就可以了。
那接着问题来了,
g
e
t
a
l
l
[
i
]
[
j
]
getall[i][j]
getall[i][j] 又怎么求呢?
我们再让
g
e
t
d
i
s
[
i
]
[
j
]
[
k
]
getdis[i][j][k]
getdis[i][j][k] 表示在第
i
i
i 个树中,
j
j
j 点和
k
k
k 点的距离,那我们就可以推出这个,,用递归来求:
- 当 j j j 位于 a [ i ] a[i] a[i] 这个子树中时 g e t a l l [ i ] [ j ] = g e t a l l [ a [ i ] ] [ j ] + g e t a l l [ b [ i ] ] [ d [ i ] ] + ( l [ i ] + g e t d i s [ a [ i ] ] [ c [ i ] ] [ j ] ) ∗ s i z e [ b [ i ] ] getall[i][j] = getall[a[i]][j] + getall[b[i]][d[i]] + (l[i] + getdis[a[i]][c[i]][j]) * size[b[i]] getall[i][j]=getall[a[i]][j]+getall[b[i]][d[i]]+(l[i]+getdis[a[i]][c[i]][j])∗size[b[i]]
- 当
j
j
j 位于
b
[
i
]
b[i]
b[i] 这个子树中时
g
e
t
a
l
l
[
i
]
[
j
]
=
g
e
t
a
l
l
[
b
[
i
]
]
[
j
−
s
i
z
e
[
i
]
]
+
g
e
t
a
l
l
[
a
[
i
]
]
[
c
[
i
]
]
+
(
l
[
i
]
+
g
e
t
d
i
s
[
b
[
i
]
]
[
d
[
i
]
]
[
j
−
s
i
z
e
[
i
]
]
)
∗
s
i
z
e
[
a
[
i
]
]
getall[i][j] = getall[b[i]][j-size[i]] + getall[a[i]][c[i]] + (l[i] + getdis[b[i]][d[i]][j-size[i]]) * size[a[i]]
getall[i][j]=getall[b[i]][j−size[i]]+getall[a[i]][c[i]]+(l[i]+getdis[b[i]][d[i]][j−size[i]])∗size[a[i]]
这里 j j j 要变成 j − s i z e [ i ] j-size[i] j−size[i] ,因为这个:(下面的也同理)
好,那现在又变成了求
g
e
t
d
i
s
[
i
]
[
j
]
[
k
]
getdis[i][j][k]
getdis[i][j][k] 。
那我们也可以用递归求出:
- 如果两个位置都在左子树,就直接递归左子树
- 如果两个位置都在右子树,就直接递归右子树
- 不然就
g
e
t
d
i
s
[
i
]
[
j
]
[
k
]
=
g
e
t
d
i
s
[
a
[
i
]
]
[
j
]
[
c
[
i
]
]
+
l
[
i
]
+
g
e
t
d
i
s
[
b
[
i
]
]
[
k
−
s
i
z
e
[
a
[
i
]
]
]
[
d
[
i
]
]
getdis[i][j][k]=getdis[a[i]][j][c[i]] + l[i] + getdis[b[i]][k-size[a[i]]][d[i]]
getdis[i][j][k]=getdis[a[i]][j][c[i]]+l[i]+getdis[b[i]][k−size[a[i]]][d[i]]
(为什么要减 s i z e [ a [ i ] ] size[a[i]] size[a[i]] 就不多说了,跟上面一样的道理)
要注意的是要用
l
o
n
g
l
o
n
g
long\ long
long long ,而且要记得取模。
还有就是一开始的时候不要就直接给
s
i
z
e
[
i
]
size[i]
size[i] 取模,因为后面的运算中就比如前面说到的要减
s
i
z
e
[
a
[
i
]
]
size[a[i]]
size[a[i]] ,如果你取模了,就会对答案造成影响。
而且递推和算答案的时候多加几个取模,
s
i
z
e
[
i
]
size[i]
size[i] 这种的最好先单独取模(因为读入的时候不能取模,前面说了),不然你开
l
o
n
g
l
o
n
g
long\ long
long long 都会在运算的时候爆掉。
哦对了,我们还要用两个
m
a
p
map
map 分别储存
g
e
t
a
l
l
[
i
]
[
j
]
getall[i][j]
getall[i][j] 和
g
e
t
d
i
s
[
i
]
[
j
]
[
k
]
getdis[i][j][k]
getdis[i][j][k] 。
(就是记忆化,下次如果遇到一样的可以直接得出结果)
代码
#include<map>
#include<cstdio>
#define ll long long
#define mo 1000000007
using namespace std;
const ll N = 65;
ll T;
ll m, a[N], b[N], c[N], d[N], l[N];
ll size[N], ans[N];
map <ll, ll> remall[N];
map <pair <ll, ll>, ll> rem[N];
ll getdis(ll k, ll x, ll y) {//取得k树中两点间的距离
if (!k) return 0;
if (x == y) return 0;
if (x > y) swap(x, y);
pair <ll, ll> tmp = make_pair(x, y);
if (rem[k][tmp]) return rem[k][tmp];//记忆化
if (y < size[a[k]]) return rem[k][tmp] = getdis(a[k], x, y);//都在左子树
if (x >= size[a[k]]) return rem[k][tmp] = getdis(b[k], x - size[a[k]], y - size[a[k]]);//都在右子树
rem[k][tmp] = (getdis(a[k], x, c[k]) + l[k]) % mo;
rem[k][tmp] = (rem[k][tmp] + getdis(b[k], y - size[a[k]], d[k])) % mo;
return rem[k][tmp];
}
ll getall(ll k, ll x) {//取得k树中所有点到x点的距离之和
if (!k) return 0;
if (remall[k][x]) return remall[k][x];//记忆化
if (x < size[a[k]]) {//在左子树
remall[k][x] = (getall(a[k], x) + getall(b[k], d[k])) % mo;
remall[k][x] = (remall[k][x] + (l[k] + getdis(a[k], c[k], x)) % mo * (size[b[k]] % mo) % mo) % mo;
return remall[k][x];
}
//在右子树
remall[k][x] = (getall(b[k], x - size[a[k]]) + getall(a[k], c[k])) % mo;
remall[k][x] = (remall[k][x] + (l[k] + getdis(b[k], d[k], x - size[a[k]])) % mo * (size[a[k]] % mo) % mo) % mo;
return remall[k][x];
}
int main() {
scanf("%lld", &T);//读入
for (ll times = 1; times <= T; times++) {
scanf("%lld", &m);//读入
size[0] = 1;
for (ll i = 1; i <= m; i++) {
remall[i].clear();
rem[i].clear();
scanf("%lld %lld %lld %lld %lld", &a[i], &b[i], &c[i], &d[i], &l[i]);//读入
size[i] = size[a[i]] + size[b[i]];//求出长度(这里别取模)
ans[i] = (ans[a[i]] + ans[b[i]]) % mo;
ans[i] = (ans[i] + getall(a[i], c[i]) * (size[b[i]] % mo) % mo) % mo;
ans[i] = (ans[i] + getall(b[i], d[i]) * (size[a[i]] % mo) % mo) % mo;
ans[i] = (ans[i] + (size[a[i]] % mo) % mo * (size[b[i]] % mo) % mo * (l[i] % mo) % mo) % mo;
printf("%lld\n", ans[i]);//输出
}
}
return 0;
}