题意见 CF-282-DIV1-D
解法有两种,分为在线和离线,先讲离线的方法。
首先进行一次DFS, 将整棵树表示为欧垃序列,这样对于每个节点,他的子树就在欧垃序列上的一个区间内。
再把问题化为离线,对于每个节点u,将所有需要询问f(u, v)的v点维护在一个vector内。
现在精髓来了,我们再次遍历这棵树,在遍历的过程中维护两个数组,a[]和b[]。
假设我们现在遍历到了点u,则a[v]和b[v]分别维护了dis(u, v)^2, dis(u, v)。
假设u有一个子节点s,当我们准备从u转移到s时,更新a[]和b[]的值。
假设s的子树在欧垃序列上的区间为l到r,于是b[l]~b[r]的值要减去dis(u, s), 而其他b[]的值则要加上dis(u, s)。
对于a[]也可以用于b[]类似的方法来进行维护更新。
于是对于每个询问f(u, v),当我们走到节点u时,假设v代表子树在欧垃序列上的左右边界分别为l和r。
则f(u, v) = sum(a[l], a[l+1], ...a[r]) - (sum(a[1], a[2], ...a[n]) - sum(a[l], a[l+1], ...a[r]))
这就是一个区间求和的操作,于是显然我们可以用一棵线段树来同时维护a[]和b[]。
最终复杂度O(mlogn)
下面是离线方法的代码
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <vector>
#include <math.h>
#include <queue>
#include <map>
#include <set>
#include <algorithm>
using namespace std;
#define FOR(i, j, k) for(int i=(j);i<=(k);i++)
#define REP(i, n) for(int i=0;i<(n);i++)
#define mst(x, y) memset(x, y, sizeof(x));
#define pii pair<int, int>
#define fr first
#define sc second
#define left myleft
#define right myright
#define ll long long
#define ull unsigned long long
#define seed 1331
#define mod ((int)1e9+7)
#define eps 1e-5
int mul(int a, int b){
long long tmp = (ll)a*(ll)b;
return ((tmp%mod)+mod)%mod;
}
int sub(int a, int b);
int add(int a, int b){
if(b < 0)return sub(a, -b);
return (a+b>mod)?a+b-mod:a+b;
}
int sub(int a, int b){
if(b < 0)return add(a, -b);
return (a-b<0)?a-b+mod:a-b;
}
int n, ans[100009], m, timeStramp, idx[100009], init_len[100009];
vector <pii> edge[100009], query[100009];
pii lim[100009];
void dfs0(int u, int fa){
lim[u].fr = idx[u] = ++timeStramp;
REP(i, edge[u].size()) if(fa != edge[u][i].fr)
dfs0(edge[u][i].fr, u);
lim[u].sc = timeStramp;
}
void dfs1(int u, int fa, int len){
init_len[idx[u]] = len;
REP(i, edge[u].size()) if(fa != edge[u][i].fr)
dfs1(edge[u][i].fr, u, add(len, edge[u][i].sc));
}
struct Node{
int l, r, sum, sq, flag;
}tree[100009*4];
void update(int u){
tree[u].sum = add(tree[u*2].sum, tree[u*2+1].sum);
tree[u].sq = add(tree[u*2].sq, tree[u*2+1].sq);
}
void build(int u, int l, int r){
tree[u].l=l; tree[u].r=r;
tree[u].flag = 0;
if(l == r){
tree[u].sum = init_len[l];
tree[u].sq = mul(init_len[l], init_len[l]);
return ;
}
int mid = l+r>>1;
build(u*2, l, mid);
build(u*2+1, mid+1, r);
update(u);
}
void goo(int u, int c){
int tmp = mul(c, 2);
tmp = mul(tmp, tree[u].sum);
tree[u].sq = add(tree[u].sq, tmp);
tmp = mul(tree[u].r-tree[u].l+1, mul(c, c));
tree[u].sq = add(tree[u].sq, tmp);
tree[u].sum = add(tree[u].sum, mul(tree[u].r-tree[u].l+1, c));
tree[u].flag = add(tree[u].flag, c);
}
void push(int u){
if(tree[u].flag == 0) return ;
goo(u*2, tree[u].flag);
goo(u*2+1, tree[u].flag);
tree[u].flag = 0;
}
int find_(int u, int l, int r){
if(tree[u].l==l && tree[u].r==r) return tree[u].sq;
push(u);
int mid = tree[u].l+tree[u].r>>1;
if(r <= mid) return find_(u*2, l, r);
else if(l > mid) return find_(u*2+1, l, r);
return add(find_(u*2, l, mid), find_(u*2+1, mid+1, r));
}
void add(int u, int l, int r, int c){
if(tree[u].l==l&&tree[u].r==r){
goo(u, c);
return ;
}
push(u);
int mid = tree[u].l+tree[u].r>>1;
if(r <= mid) add(u*2, l, r, c);
else if(l > mid) add(u*2+1, l, r, c);
else add(u*2, l, mid, c), add(u*2+1, mid+1, r, c);
update(u);
}
void dfs2(int u, int fa){
REP(i, query[u].size()){
int id = query[u][i].fr, v = query[u][i].sc;
int l = lim[v].fr, r = lim[v].sc;
int insub = 0, outsub = 0;
insub = find_(1, l, r);
if(l != 1) outsub = add(outsub, find_(1, 1, l-1));
if(r != n) outsub = add(outsub, find_(1, r+1, n));
ans[id] = sub(insub, outsub);
}
REP(i, edge[u].size()){
int v = edge[u][i].fr, c = edge[u][i].sc;
if(v == fa) continue;
int l = lim[v].fr, r = lim[v].sc;
add(1, l, r, -c);
if(l != 1) add(1, 1, l-1, c);
if(r != n) add(1, r+1, n, c);
dfs2(v, u);
add(1, l, r, c);
if(l != 1) add(1, 1, l-1, -c);
if(r != n) add(1, r+1, n, -c);
}
}
int main(){
cin>>n;
REP(i, n-1){
int u, v, c;
cin>>u>>v>>c;
edge[u].push_back(pii(v, c));
edge[v].push_back(pii(u, c));
}
cin>>m;
REP(i, m){
int u, v;
cin>>u>>v;
query[u].push_back(pii(i, v));
}
timeStramp = 0;
dfs0(1, 0);
dfs1(1, 0, 0);
build(1, 1, n);
dfs2(1, 0);
REP(i, m) printf("%d\n", ans[i]);
}
接下来是动态的方法。
个人觉得比离线要复杂一点。
首先进行一次dfs, 算出这三个东西, sons[], subsum[], subsq[], 分别代表每个节点子树的节点数,子树节点到它的距离和,子树节点到它距离的平方的和。
然后再进行一次dfs,这次dfs算出两个东西, totsum[], totsq[], 分别表示其他所有节点到当前节点的距离和,其他所有节点到当前节点的距离的平方的和。
具体的计算方法很简单。
首先对于1号根节点,显然totsum[1]=subsum[1], totsq[1]=subsq[1],
然后从1号节点开始,dfs的过程中用父节点去更新儿子节点,更具体的更新方法不细说,可以打开(x+a)^2+(y+a)^2这个方程想一想。
最后再进行一次dfs, 这次dfs用来初始化倍增算法的LCA,后面会用来算两个节点的祖先和距离。
经过三次dfs后初始化工作算是完成了,
现在依次处理每个询问,
对于每个询问f(u, v), 分两种情况,一种为u为v的子节点,一种为u不是v的子节点,分别考虑。
然后使用之前求出的totsum, totsq, subsum, subsq, sons和dis(u, v){这个可以用LCA求出}
就可以处理得出f(u, v)了。