送你一颗圣诞树

题意

m 棵树T0~ Tm T0 只有一个 0 号节点。对于Ti(i[1,m]),给出 ai,bi,ci,di,li ,表示这棵树是由 Tai ci 号节点与 Tbi di 号节点间连一条长为 li 的边构成的。在 Ti 中,保持 Tai 中的所有节点编号不变,然后如果 Tai 中有 s 个节点,他会把Tbi 中的所有节点的编号加上 s
对于Ti(i[1,m]),求 i=0n1j=i+1n1d[i][j] ,其中 n Ti节点个数, d[i][j] 为该树中 i,j 的最短距离,输出 mod 109+7

0ai,bi<i,0li109
1m60
数据组数 T100

Time Limits:2000ms
Memory Limits:512M

分析

ans[i] Ti 的答案, size[i] Ti 的节点个数。
易得 ans[i]=ans[ai]+ans[bi]+size[ai]size[bi]li+topai,cisize[bi]+topbi,disize[ai]
topk,x 表示 Tk 中所有节点到 x 号节点的距离和。
现在我们看看如何求topk,x
考虑把 Tk 分成 Tak,Tbk
k=0 时,值为 0 ;
x<size[ak]时, x 原来是在Tak中,那么显然 topk,x=topak,x+(topbk,dk+(lk+disak,ck,x)size[bk])
其中 disk,x,y 表示 Tk x,y 的距离。
x>=size[ak] 时, x 原来是在Tbk中,仿照2°,我们可以得出一个相似的方程。
这样就可以递归求解 topk,x 了。
我们的问题又变成了如何求 disk,x,y
同样考虑递归求解。
如果 x,y 同属于组成 Tk 的两棵树中的一棵,我们可以递归下去;否则令 x<y disk,x,y=disak,x,ck+disbk,y,dk+lk ,这样变成了两个子问题,继续递归求解。边界为 k=0x=y 时, disk,x,y=0

上面 top,dis 的求值过程可以用记忆化搜索。

disk,x,y 的过程最多递归 m 层,时间复杂度为O(m);
topk,x,y 的过程最多有 m 层,每层一个求dis的过程,时间复杂度为 O(m2)
所以总时间复杂度是 O(m3)

代码

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>
using namespace std;

typedef long long LL;
typedef pair<LL,LL> pa;
const int N = 66;
const LL P = 1e9 + 7;
int n,a[N],b[N],len[N];
LL size[N],ans[N],c[N],d[N];
map< pa , int > dis[N];

void init() {
    scanf("%d",&n);
    for (int i = 1;i <= n;i ++) {
        scanf("%d%d%lld%lld%d",&a[i],&b[i],&c[i],&d[i],&len[i]);
        dis[i].clear();
    }
}

int get(int k,LL x,LL y) {
    if (k == 0 || x == y) return 0;
    if (y < x) swap(x,y);
    if (y < size[a[k]]) return get(a[k],x,y);
    if (x >= size[a[k]]) return get(b[k],x - size[a[k]],y - size[a[k]]);
    pa cur = make_pair(x,y);
    if (dis[k].find(cur) != dis[k].end()) return dis[k][cur];
    int re = (0LL + get(a[k],x,c[k]) + get(b[k],y - size[a[k]],d[k]) + len[k]) % P;
    return dis[k][cur] = re;
}

int top(int k,LL x) {
    if (k == 0) return 0;
    pa cur = make_pair(x,-1);
    if (dis[k].find(cur) != dis[k].end()) return dis[k][cur];
    int re = 0;
    if (x < size[a[k]]) {
        re = top(a[k],x);
        re = (re + top(b[k],d[k])) % P;
        re = (re + 1LL * (get(a[k],x,c[k]) + len[k]) % P * (size[b[k]] % P) % P) % P;
    }       
    else {
        re = top(b[k],x - size[a[k]]);
        re = (re + top(a[k],c[k])) % P;
        re = (re + 1LL * size[a[k]] % P * (get(b[k],x - size[a[k]],d[k]) + len[k]) % P) % P;
    }
    return dis[k][cur] = re;
}

void solve() {
    for (int i = 1;i <= n;i ++) {
        int l = a[i],r = b[i];
        ans[i] = (ans[l] + ans[r]) % P;
        size[i] = size[l] + size[r];
        ans[i] = (ans[i] + size[l] % P * (size[r] % P) % P * LL(len[i]) % P) % P;
        ans[i] = (ans[i] + 1LL * size[l] % P * top(r,d[i]) % P) % P;
        ans[i] = (ans[i] + 1LL * size[r] % P * top(l,c[i]) % P) % P;
        printf("%lld\n",ans[i]);
    }
}

int main() {
    int T;
    scanf("%d",&T);
    size[0] = 1;
    while (T --) {
        init();
        solve();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值