题意:给定一颗树,有 $m$ 次操作.
操作 0 :向集合 $S$ 中加入一条路径 $(p,q)$,权值为 $v$
操作 1 :给定一个点集 $T$,求 $T$ 的并集与 $S$ 中路径含交集的权和.(就是如果路径 $i$ 与 $T$ 有交集,就产生 $v_{i}$ 的贡献)
数据范围:路径长度 $leqslant 20$,$1leqslant n,m leqslant 10^5$
如果路径长度为 0 (即 $S$ 中全部是点)的话我们求的就是点集 $T$ 的树链的并的权和.
这个可以用 DFS 序 + 树状数组来维护.
树上一个重要的性质就是任意两点之间如果经过 $i$ 个点的话会经过 $i-1$ 条边,边数总是点数-1.
然后下一步就特别神了:
对于新加入的一条路径:将路径上的点加上权值,边加上权值的相反数.
你发现如果一个连通块与这条路径有并集的话必经过 $i$ 个点和 $i-1$ 条边.
即边数恒等于点数 - 1,这就实现了只贡献一次的效果.
对于维护一个点到根的权和,我们采用 DFS 序 + 树状数组的方式.
#include <vector> #include <cstdio> #include <set> #include <cstring> #include <string> #include <algorithm> #define N 200007 #define ll long long using namespace std; void setIO(string s) { string in=s+".in"; string out=s+".out"; freopen(in.c_str(),"r",stdin); freopen(out.c_str(),"w",stdout); } struct BIT { ll C[N]; int lowbit(int t) { return t&(-t); } void update(int x,int v) { while(x<N) { C[x]+=(ll)v; x+=lowbit(x); } } ll query(int x) { ll tmp=0; while(x>0) { tmp+=C[x]; x-=lowbit(x); } return tmp; } }tree; int tot; int n,m,L; int edges; int dfn; int hd[N]; int to[N<<1]; int nex[N<<1]; int top[N]; int son[N]; int size[N]; int dep[N]; int A[N]; int fa[N]; int st[N]; int ed[N]; int Fa[N]; vector<int>G[N]; bool cmp(int a,int b) { return st[a]<st[b]; } void add(int u,int v) { nex[++edges]=hd[u]; hd[u]=edges; to[edges]=v; } void dfs1(int u,int ff) { fa[u]=ff; size[u]=1; dep[u]=dep[ff]+1; for(int i=hd[u];i;i=nex[i]) { int v=to[i]; if(v==ff) { continue; } dfs1(v,u); size[u]+=size[v]; if(size[v]>size[son[u]]) { son[u]=v; } } } void dfs2(int u,int tp) { top[u]=tp; if(son[u]) { dfs2(son[u],tp); } for(int i=hd[u];i;i=nex[i]) { if(to[i]!=fa[u]&&to[i]!=son[u]) { dfs2(to[i],to[i]); } } } int LCA(int x,int y) { while(top[x]!=top[y]) { dep[top[x]]>dep[top[y]]?x=fa[top[x]]:y=fa[top[y]]; } return dep[x]<dep[y]?x:y; } // 到根的权和 ll Sum(int x) { return tree.query(st[x]); } void build(int u) { for(int i=hd[u];i;i=nex[i]) { int v=to[i]; if(v==fa[u]) { continue; } ++tot; Fa[tot]=u; Fa[v]=tot; G[u].push_back(tot); G[tot].push_back(v); build(v); } } void dfs(int u) { st[u]=++dfn; for(int i=0;i<G[u].size();++i) { int v=G[u][i]; // printf("%d %d ",u,v); dfs(v); } ed[u]=dfn; } int main() { setIO("tree"); int i,j; scanf("%d%d%d",&n,&m,&L); for(i=1;i<n;++i) { int x,y; scanf("%d%d",&x,&y); add(x,y); add(y,x); } dfs1(1,0); dfs2(1,1); tot=n; build(1); dfs(1); while(m--) { int op; scanf("%d",&op); if(op==0) { int p,q,v,d=1; scanf("%d%d%d",&p,&q,&v); int lca=LCA(p,q); while(p!=lca) { tree.update(st[p],d*v); tree.update(ed[p]+1,-d*v); d*=-1; p=Fa[p]; } d=1; while(q!=lca) { tree.update(st[q],d*v); tree.update(ed[q]+1,d*v); d*=-1; q=Fa[q]; } tree.update(st[lca],v); tree.update(ed[lca]+1,-v); } else { int a,cnt=0; scanf("%d",&a); for(i=1;i<=a;++i) { scanf("%d",&A[++cnt]); } scanf("%d",&a); for(i=1;i<=a;++i) { scanf("%d",&A[++cnt]); } sort(A+1,A+1+cnt,cmp); int lca=A[1]; for(i=2;i<=cnt;++i) { lca=LCA(lca,A[i]); } ll ans=0; for(i=1;i<=cnt;++i) { ans+=Sum(A[i])-Sum(Fa[lca]); } for(i=2;i<=cnt;++i) { ans-=Sum(LCA(A[i],A[i-1]))-Sum(Fa[lca]); } printf("%lld ",ans); } } return 0; }