原题链接戳这儿

SOLUTION

考虑一种非常\(naive\)的统计方法,就是对于每一个点\(u\),我们维护它能到达的点集\(S_u\),最后答案就是\(\frac{\sum\limits_{i=1}^{n}|S_i|}{2}\)

也就是说我们可以先树剖一下,对于每一个点都开一棵线段树,每次修改\(O(nlogn)\)地更新一下路径上的线段树,最后查询一下就行了

但是这样的复杂度是\(O(n^2log^2n)\)的,显然会炸。注意到每次是对一条链上的所有点操作,所以我们可以查分。又因为差分之后要把子树的贡献传上去,再上个线段树合并就行了,复杂度降为\(O(nlog^2n)\)

代码也比较好写,细节不多

#include <bits/stdc++.h>

using namespace std;

#define N 100000
#define ll long long
#define mp make_pair
#define pii pair<int, int>
#define pb push_back
#define mid ((l + r) >> 1) int n, m;
vector<int> G[N + 5];
int sz[N + 5], fa[N + 5], d[N + 5], hson[N + 5], top[N + 5], dfn[N + 5], dfn_clk, id[N + 5];
int nid, root[N + 5], sumv[N << 7], ch[2][N << 7], addv[N << 7];
vector<pii> cf[N + 5], segs[N + 5];
ll ans; void dfs1(int u, int pa) {
sz[u] = 1;
fa[u] = pa;
for (int i = 0, v; i < G[u].size(); ++i) {
v = G[u][i];
if (v == pa) continue;
d[v] = d[u] + 1;
dfs1(v, u);
sz[u] += sz[v];
if (sz[v] > sz[hson[u]]) hson[u] = v;
}
} void dfs2(int u, int tp) {
dfn[u] = ++dfn_clk;
id[dfn_clk] = u;
top[u] = tp;
if (hson[u]) dfs2(hson[u], tp);
for (int i = 0, v; i < G[u].size(); ++i) {
v = G[u][i];
if (v == fa[u] || v == hson[u]) continue;
dfs2(v, v);
}
} int lca(int x, int y) {
while (top[x] != top[y]) d[top[x]] > d[top[y]] ? x = fa[top[x]] : y = fa[top[y]];
return d[x] < d[y] ? x : y;
} void addModify(int x, int s, int t) {
int z = lca(s, t);
cf[s].pb(mp(x, 1)), cf[t].pb(mp(x, 1));
cf[z].pb(mp(x, -1)), cf[fa[z]].pb(mp(x, -1));
while (top[s] != top[z]) segs[x].pb(mp(dfn[top[s]], dfn[s])), s = fa[top[s]];
segs[x].pb(mp(dfn[z], dfn[s]));
while (top[t] != top[z]) segs[x].pb(mp(dfn[top[t]], dfn[t])), t = fa[top[t]];
if (dfn[z] + 1 <= dfn[t]) segs[x].pb(mp(dfn[z] + 1, dfn[t]));
} void pushup(int o, int l, int r) {
if (addv[o]) sumv[o] = r - l + 1;
else sumv[o] = sumv[ch[0][o]] + sumv[ch[1][o]];
} void add(int &o, int l, int r, int L, int R, int k) {
if (!o) o = ++nid;
if (L <= l && r <= R) {
addv[o] += k;
pushup(o, l, r);
return ;
}
if (L <= mid) add(ch[0][o], l, mid, L, R, k);
if (R > mid) add(ch[1][o], mid + 1, r, L, R, k);
pushup(o, l, r);
} void merge(int &o, int u, int l, int r) {
if (!o || !u) {
if (!o) o = u;
return ;
}
addv[o] += addv[u];
if (l < r) {
merge(ch[0][o], ch[0][u], l, mid);
merge(ch[1][o], ch[1][u], mid + 1, r);
}
pushup(o, l, r);
} void dfs(int u) {
for (int i = 0, v; i < G[u].size(); ++i) {
v = G[u][i];
if (v == fa[u]) continue;
dfs(v);
merge(root[u], root[v], 1, n);
}
for (int i = 0; i < cf[u].size(); ++i)
for (int j = 0; j < segs[cf[u][i].first].size(); ++j)
add(root[u], 1, n, segs[cf[u][i].first][j].first, segs[cf[u][i].first][j].second, cf[u][i].second);
ans += max(0, sumv[root[u]] - 1); // 注意这里要与0取max
} int main() {
scanf("%d%d", &n, &m);
for (int i = 1, x, y; i < n; ++i) {
scanf("%d%d", &x, &y);
G[x].pb(y), G[y].pb(x);
}
dfs1(1, 0), dfs2(1, 1);
for (int i = 1, s, t; i <= m; ++i) {
scanf("%d%d", &s, &t);
addModify(i, s, t);
}
dfs(1);
ans /= 2;
printf("%lld\n", ans);
return 0;
}

[ZJOI2019]语言——树剖+树上差分+线段树合并的更多相关文章

  1. [Luogu5327][ZJOI2019]语言(树上差分+线段树合并)

    首先可以想到对每个点统计出所有经过它的链的并所包含的点数,然后可以直接得到答案.根据实现不同有下面几种方法.三个log:假如对每个点都存下经过它的链并S[x],那么每新加一条路径进来的时候,相当于在路 ...

  2. [BZOJ3307] 雨天的尾巴(树上差分+线段树合并)

    [BZOJ3307] 雨天的尾巴(树上差分+线段树合并) 题面 给出一棵N个点的树,M次操作在链上加上某一种类别的物品,完成所有操作后,要求询问每个点上最多物品的类型. N, M≤100000 分析 ...

  3. [Vani有约会]雨天的尾巴(树上差分+线段树合并)

    首先村落里的一共有n座房屋,并形成一个树状结构.然后救济粮分m次发放,每次选择两个房屋(x,y),然后对于x到y的路径上(含x和y)每座房子里发放一袋z类型的救济粮. 然后深绘里想知道,当所有的救济粮 ...

  4. Luogu5327 ZJOI2019语言(树上差分+线段树合并)

    暴力树剖做法显然,即使做到两个log也不那么优美. 考虑避免树剖做到一个log.那么容易想到树上差分,也即要对每个点统计所有经过他的路径产生的总贡献(显然就是所有这些路径端点所构成的斯坦纳树大小),并 ...

  5. bzoj 3307: 雨天的尾巴【树剖lca+树上差分+线段树合并】

    这居然是我第一次写线段树合并--所以我居然在合并的时候加点结果WAWAWAMLEMLEMLE--!ro的时候居然直接指到la就行-- 树上差分,每个点建一棵动态开点线段树,然后统计答案的时候合并即可 ...

  6. BZOJ4999:This Problem Is Too Simple!(DFS序&树上差分&线段树动态开点:区间修改单点查询)

    Description 给您一颗树,每个节点有个初始值. 现在支持以下两种操作: 1. C i x(0<=x<2^31) 表示将i节点的值改为x. 2. Q i j x(0<=x&l ...

  7. 2018.08.28 洛谷P4556 [Vani有约会]雨天的尾巴(树上差分+线段树合并)

    传送门 要求维护每个点上出现次数最多的颜色. 对于每次修改,我们用树上差分的思想,然后线段树合并统计答案就行了. 注意颜色很大需要离散化. 代码: #include<bits/stdc++.h& ...

  8. BZOJ 3307 雨天的尾巴 (树上差分+线段树合并)

    题目大意:给你一棵树,树上一共n个节点,共m次操作,每次操作给一条链上的所有节点分配一个权值,求所有节点被分配到所有的权值里,出现次数最多的权值是多少,如果出现次数相同就输出最小的. (我辣鸡bzoj ...

  9. P4556 [Vani有约会]雨天的尾巴 /【模板】线段树合并 (树上差分+线段树合并)

    显然的树上差分问题,最后要我们求每个点数量最多的物品,考虑对每个点建议线段树,查询子树时将线段树合并可以得到答案. 用动态开点的方式建立线段树,注意离散化. 1 #include<bits/st ...

随机推荐

  1. C#实现排列、组合

    排列组合的概念 排列:从n个不同元素中取出m(m≤n)个元素,按照一定的顺序排成一列,叫做从n个元素中取出m个元素的一个排列(Arrangement). 组合:从m个不同的元素中,任取n(n≤m)个元 ...

  2. prometheus 监控 redis + rabbitmq

    1.安装部署 1.1 wget https://github.com/oliver006/redis_exporter/releases/download/v0.15.0/redis_exporter ...

  3. Java代码是怎么运行的

    前言.... 作为一名 Java 程序员,你应该知道,Java 代码有很多种不同的运行方式.比如说可以在开发工具中运行,可以双击执行 jar 文件运行,也可以在命令行中运行,甚至可以在网页中运行.当然 ...

  4. Websocket基础梳理

    Websocket原理: websocket介绍: WebSocket(http://dev.w3.org/html5/websockets)是HTML5规范(http://www.w3.org/TR ...

  5. C++编写DLL文件

    动态链接库DLL文件与EXE文件一样也是可执行文件,但是DLL也被称为库,因为里面封装了各种类.函数之类的东西,就像一个库一样,存着很多东西,主要是用来调用的.调用方式主要分为两种:隐式(通过lib文 ...

  6. Simple Library Management System HDU - 1497(图书管理系统)

    Problem Description After AC all the hardest problems in the world , the ACboy 8006 now has nothing ...

  7. Date及DateFormat用法

    Date 与DateFormat之间的转化String <————>Date Date与Calendar 之间的转化Long<————>Date 日历小程序 Scanner i ...

  8. sql 行数据找出最大的及所有数据最大的

    SELECT @charges=ISNULL(MAX(a.maxcharge), 0.00) FROM( SELECT (SELECT MAX(maxcharge) FROM(VALUES(ilong ...

  9. ECMAScript中的原型继承

    //ECMAScript中的原型继承//ECMAScript中的继承主要是依靠原型链实现的.(关于原型链的介绍,详见<高三>6.3.1章节 P162) //本文示例主要为了说明SubTyp ...

  10. 【ES6 】ES6 解构赋值--对象解构赋值

    对象的解构与数组有一个重要的不同. 数组的元素是按次序排列的,变量的取值由它的位置决定 而对象的属性没有次序,变量必须与属性同名,才能取到正确的值. 基本用法 如果解构失败,变量的值等于undefin ...