传送门

首先求出缩一个点 $x$ 的贡献,就是缩 $x$ 的父亲的贡献加上 $x$ 的子树多减少的深度

假设此时缩父亲的贡献已经考虑过了,那么 $x$ 的子树多减少的深度就是子树的节点数

注意此时要满足 $x$ 不是根节点或根节点的儿子,不然缩和没缩是一样的

设这个贡献为 $sum[x]$

然后把所有操作 $l,r,v$ 离线,遇到一个操作左端点就把 $v$ 插入 $set$,遇到右端点再取出,在过程中动态维护总贡献

考虑把每个当前加入的节点搞一个虚树

对于一次缩节点的操作,如果它不在虚树链上,只会影响 $x$ 与虚树的第一个交点的一条链,更上面的已经缩过了

设交点为 $u$,那么贡献就是 $sum[x]-sum[u]$

如果原本已经在虚树链上了,那么 $x$ 不会有贡献

现在问题是如何求与虚树的交点,考虑原本构造虚树的过程,把节点按 $dfn$ 排序,然后根据与前后节点的 $lca$ 确定具体连边

设前后节点为 $u,v$,如果 $LCA(u,x)=u$ 且 $LCA(v,x)=x$ 那么 $x$ 在虚树边 $(u,v)$ 上,不产生贡献

如果 $LCA(u,x)!=u$ 且 $LCA(v,x)=x$ 那么 $x$ 还是在虚树上 $v$ 到根的路径上,不产生贡献

如果 $LCA(u,x)=u$ 且 $LCA(v,x)!=x$ ,或者 $LCA(u,x)!=u$ 且 $LCA(v,x)!=x$ 那么说明 $x$ 有多出来一段不在虚树还没统计的贡献

显然多出来的一段是 $LCA(u,x),LCA(v,x)$ 中深度较大的节点 $p$ 与 $x$ 的一条链的贡献,贡献为 $sum[x]-sum[p]$

然后就可以写成代码实现了,但是发现对于前两种情况 $LCA(u,x),LCA(v,x)$ 中深度较大的节点 $p$ 就是 $x$,贡献其实就是 $sum[x]-sum[p]=0$

所以直接求 $p$ 就行,不用分情况讨论了

因为一个节点可以被多次插入,所以要用 $multiset$,$multiset$ 里节点按 $dfn$ 排序就可以直接找前驱后继了

具体看代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<set>
using namespace std;
typedef long long ll;
inline int read()
{
int x=,f=; char ch=getchar();
while(ch<''||ch>'') { if(ch=='-') f=-; ch=getchar(); }
while(ch>=''&&ch<='') { x=(x<<)+(x<<)+(ch^); ch=getchar(); }
return x*f;
}
const int N=2e5+;
int n,m,Q;
vector <int> V[N],add[N],del[N];
int dfn[N],F[][N],dep[N],sz[N],cnt;
ll sum[N],ans;
void dfs1(int x)
{
sz[x]=; dfn[x]=++cnt; ans+=dep[x]-;
for(int i=;i<=;i++) F[i][x]=F[i-][F[i-][x]];
for(int v : V[x])
{
if(v==F[][x]) continue;
F[][v]=x; dep[v]=dep[x]+;
dfs1(v); sz[x]+=sz[v];
}
}
void dfs2(int x)
{
sum[x]=sum[F[][x]]+(dep[x]>)*sz[x];
for(int v : V[x]) if(v!=F[][x]) dfs2(v);
}
inline int LCA(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
for(int i=;i>=;i--)
if(dep[F[i][x]]>=dep[y]) x=F[i][x];
if(x==y) return x;
for(int i=;i>=;i--)
if(F[i][x]!=F[i][y]) x=F[i][x],y=F[i][y];
return F[][x];
}
//以上预处理一堆东西
struct dat{
int val,x;
dat (int v=,int xx=) { val=v,x=xx; }
inline bool operator < (const dat &tmp) const {
return val<tmp.val;
}
};
multiset <dat> S;
multiset <dat>::iterator it;
inline int Find(int x)//求x与当前虚树的交点p
{
dat res; it=S.upper_bound(dat(dfn[x],x)); int lca;
//注意上面的res和set里面的节点没有关系,只是为了方便更新res
if(it!=S.end())//注意判越界
lca=LCA(x,(*it).x),res=max(res,dat(dep[lca],lca));
if(it!=S.begin())
lca=LCA(x, (*prev(it)).x ),res=max(res,dat(dep[lca],lca));
//上面的 prev(it) 是找到与it-1指针不同的最后一个位置
return res.x ? res.x : x;
}
int main()
{
n=read(),m=read(),Q=read(); int a,b,c;
for(int i=;i<n;i++)
{
a=read(),b=read();
V[a].push_back(b); V[b].push_back(a);
}
while(Q--)
{
a=read(),b=read(),c=read();
add[a].push_back(c); del[b+].push_back(c);
}
dep[]=; dfs1(); dfs2(); S.insert(dat(dfn[],));//初始有根节点
for(int i=;i<=m;i++)
{
for(int x : add[i])
{
if(S.find(dat(dfn[x],x))==S.end()) ans-=(sum[x]-sum[Find(x)]);//第一次插入
S.insert(dat(dfn[x],x));
}
for(int x : del[i])
{
S.erase(S.find( dat(dfn[x],x) ));
if(S.find(dat(dfn[x],x))==S.end()) ans+=(sum[x]-sum[Find(x)]);//同理
}
printf("%lld ",ans);
}
printf("\n");
return ;
}

hihocoder1954 : 压缩树的更多相关文章

  1. hihoCoder #1954 : 压缩树(虚树)

    题意 有一棵 \(n\) 个节点且以 \(1\) 为根的树,把它复制成 \(m\) 个版本,有 \(q\) 次操作,每次对 \([l, r]\) 这些版本的 \(v\) 节点到根的路径收缩起来. 收缩 ...

  2. unsolved question's solution

    因为很懒,没有时间,只会口胡等等原因,所以有些题目就不打code了 $luogu:$ P1973 [NOI2011]Noi嘉年华: 时间离散化,预处理一个区间$[l,r]$内的最多活动个数$in[l] ...

  3. 广工赛-hdu6469-树链压缩/二分

    比较复杂的一题.. 不管是二分答案还是直接做,都需要压缩树链 /* 给定n种怪物,每个怪物有属性a[i] 打死第i种怪物后,第i只怪物会分裂成a[i]个第i-1种怪 如果打死的是第1种,那么获得经验a ...

  4. 深入理解数据库索引采用B树和B+树的原因

    前面几篇关于数据库底层磁盘文件读取,数据库索引实现细节进行了深入的研究,但是没有串联起来的讲解为什么数据库索引会采用B树和B+树而不是其他的数据结构,例如平衡二叉树.链表等,因此,本文打算从数据库文件 ...

  5. Linux内核Radix Tree(三):API介绍

    1.     单值查找radix_tree_lookup 函数radix_tree_lookup执行查找操作,查找方法是:从叶子到树顶,通过数组索引键值值查看数组元素的方法,一层层地查找slot.其列 ...

  6. 简单的理解deflate算法

    简单的理解deflate算法 最近做压缩算法. 用到了deflate压缩算法,  找了很多资料,  这篇文章算是讲的比较易懂的, 这篇文章不长,但却浅显易懂, 基本上涵盖了我想要知道的所有要点. 翻译 ...

  7. 什么是B-Tree

    B-Tree就是我们常说的B树,一定不要读成B减树,否则就很丢人了.B树这种数据结构常常用于实现数据库索引,因为它的查找效率比较高. B-Tree与二叉查找树的对比 我们知道二叉查找树查询的时间复杂度 ...

  8. VIM选项配置说明

    选项配置说明 选项书写格式 选项说明 :se[t] 显示所有被改动的选项 :se[t] all 显示所有非 termcap 选项 :se[t] termcap 显示所有 termcap 选项 :se[ ...

  9. tlflearn 编码解码器 ——数据降维用

    # -*- coding: utf-8 -*- """ Auto Encoder Example. Using an auto encoder on MNIST hand ...

随机推荐

  1. 与Swing的相识

    参考自http://c.biancheng.net/swing/ Swing是一个用于Java GUI编程(图形界面设计)的工具包(类库):换句话说,java可以用来开发带界面的PC软件,使用到的工具 ...

  2. java面向对象复习之一

    目的: 复习如何实现代码的逻辑思路: 复习类的封装: 复习类和对象的创建使用和封装: 练习: 实现功能:人到超市买东西 抽出三个类: 人 超市 东西: 功能点: 买: 它们之间的联系:东西包含于超市 ...

  3. React native 平时积累笔记

    常用插件: react-native-check-box 复选框react-native-sortable-listview 列表拖拽排序 react-native-doc-viewer 预览组件 r ...

  4. js数字每3位加一个逗号

    if(typeof val ==="number"){ var str = val.toString(); ? /(\d)(?=(\d{})+\.)/g : /(\d)(?=(?: ...

  5. minilzo使用流程

    /* testmini.c -- very simple test program for the miniLZO library This file is part of the LZO real- ...

  6. 【bzoj2064】【分裂】状态压缩表示合并子集

    (上不了p站我要死了,画师当然是wlop大大啦) 感觉这个做法还是挺难想的. 但还是总结一下思路吧.. "只可意会不可言传的状压dp"(乱说) Description 背景: 和久 ...

  7. CF 546 B Soldier and Badges(贪心)

    原题链接:http://codeforces.com/problemset/problem/546/B 原题描述: Soldier and Badges Colonel has n badges. H ...

  8. (3.3)狄泰软件学院C++课程学习剖析四

    对课程前面40课的详细回顾分析(二) 1.一个类的成员变量是对于每个对象专有的,但是成员函数是共享的. 2.构造函数只是决定一个对象的初始化状态,而不能决定对象的诞生.二阶构造人为的将初始化过程分为了 ...

  9. [BZOJ2225][SPOJ2371]LIS2 - Another Longest Increasing Subsequence Problem:CDQ分治+树状数组+DP

    分析 这回试了一下三级标题,不知道效果怎么样? 回到正题,二维最长上升子序列......嗯,我会树套树. 考虑\(CDQ\)分治,算法流程: 先递归进入左子区间. 将左,右子区间按\(x\)排序. 归 ...

  10. cefsharp 在高DPI下闪烁的问题

    今天有客户朋友说程序在他的surface下界面很闪烁,搜索了相关的资料,初步判定是DPI引起的问题,但也有可能是cefsharp 51版本在WIN10上面没有禁用GPU加速,苦于没有环境测试,所以抱着 ...