题目传送门

https://loj.ac/problem/3046

题解

首先问题就是问有多少条路径是给定的几条路径中的一条的一个子段。


先考虑链的做法。

枚举右端点 \(i\),那么求出 \(j\) 表示经过 \(i\) 的路径,左端点最小是 \(j\),那么右端点 \(i\) 的贡献就是 \(i-j+1\)。

至于求出 \(j\) 可以用直接线性地从右向左扫一遍,在右端点处枚举路径就可以了。


那么问题回到树上。

我们考虑也枚举最终的路径的一个端点。

那么,这个端点的贡献,应该就是经过这个端点的路径的并的长度。所以如果把这个端点看做根的话,那么贡献就是经过这个端点的树链的并。

根据之前做过的 bzoj3991 [SDOI2015] 寻宝游戏 的经验,树链的并的长度的二倍等于按照 dfs 序排序以后,相邻的两个点的距离的和,加上第一个点到最后一个点的距离。

那么,我们只需要能够很快地求出经过一个点 \(x\) 的路径的端点的集合,就可以通过数据结构维护出 \(x\) 的贡献了。


如何计算经过 \(x\) 的路径的端点的集合呢?

很简单,可以使用树上差分,对于路径 \(x \longleftrightarrow lca \longleftrightarrow y\),在 \(x\) 的集合中放上 \(x, y\) 两个点,在 \(y\) 的集合中放上 \(x, y\) 两个点,最后在 \(fa[lca]\) 中删去 \(x, y\)。然后使用线段树合并可以把集合递交给父节点。


感受:ZJOI 竟然有签到题。


如果使用 RMQ 求解 LCA,那么时间复杂度 \(O(n\log n)\)。

#include<bits/stdc++.h>

#define fec(i, x, y) (int i = head[x], y = g[i].to; i; i = g[i].ne, y = g[i].to)
#define dbg(...) fprintf(stderr, __VA_ARGS__)
#define File(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define fi first
#define se second
#define pb push_back template<typename A, typename B> inline char smax(A &a, const B &b) {return a < b ? a = b , 1 : 0;}
template<typename A, typename B> inline char smin(A &a, const B &b) {return b < a ? a = b , 1 : 0;} typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> pii; template<typename I> inline void read(I &x) {
int f = 0, c;
while (!isdigit(c = getchar())) c == '-' ? f = 1 : 0;
x = c & 15;
while (isdigit(c = getchar())) x = (x << 1) + (x << 3) + (c & 15);
f ? x = -x : 0;
} const int N = 1e5 + 7;
const int LOG = 18; int n, m, dfc, dfc2, nod;
ll ans;
int f[N], dfn[N], pre[N], seq[N << 1], dfn2[N], lc[N << 1][LOG], dep[N];
int rt[N]; struct Edge { int to, ne; } g[N << 1]; int head[N], tot;
inline void addedge(int x, int y) { g[++tot].to = y, g[tot].ne = head[x], head[x] = tot; }
inline void adde(int x, int y) { addedge(x, y), addedge(y, x); } inline void dfs1(int x, int fa = 0) {
f[x] = fa, dfn[x] = ++dfc, dfn2[x] = ++dfc2, pre[dfc] = seq[dfc2] = x, dep[x] = dep[fa] + 1;
for fec(i, x, y) if (y != fa) dfs1(y, x), seq[++dfc2] = x;
}
inline void rmq_init() {
for (int i = 1; i <= dfc2; ++i) lc[i][0] = seq[i];
for (int j = 1; (1 << j) <= dfc2; ++j)
for (int i = 1; i + (1 << j) - 1 <= dfc2; ++i) {
int a = lc[i][j - 1], b = lc[i + (1 << (j - 1))][j - 1];
lc[i][j] = dep[a] < dep[b] ? a : b;
}
}
inline int qmin(int l, int r) {
int k = std::__lg(r - l + 1), a = lc[l][k], b = lc[r - (1 << k) + 1][k];
return dep[a] < dep[b] ? a : b;
}
inline int lca(int x, int y) { return dfn2[x] < dfn2[y] ? qmin(dfn2[x], dfn2[y]) : qmin(dfn2[y], dfn2[x]); }
inline int dist(int x, int y) { return dep[x] + dep[y] - (dep[lca(x, y)] << 1); } struct Node { int lc, rc, val, s, ls, rs; } t[N * 120];
inline void pushup(int o) {
if (t[t[o].lc].ls) t[o].ls = t[t[o].lc].ls; else t[o].ls = t[t[o].rc].ls;
if (t[t[o].rc].rs) t[o].rs = t[t[o].rc].rs; else t[o].rs = t[t[o].lc].rs;
t[o].val = t[t[o].lc].val + t[t[o].rc].val;
if (t[t[o].lc].rs && t[t[o].rc].ls) t[o].val += dist(t[t[o].lc].rs, t[t[o].rc].ls);
t[o].s = t[t[o].lc].s + t[t[o].rc].s;
// dbg("o = %d, t[o].lc = %d, t[o].rc = %d, t[o].ls = %d, t[o].rs = %d, t[o].val = %d, t[o].s = %d\n", o, t[o].lc, t[o].rc, t[o].ls, t[o].rs, t[o].val, t[o].s);
assert((!!t[o].ls) == (!!t[o].rs));
if (t[o].ls) assert(!((t[o].val + dist(t[o].ls, t[o].rs)) & 1));
// assert((!!t[o].s) == (!!t[o].ls));
}
inline void ins(int &o, int L, int R, int x, int k) {
if (!o) o = ++nod;
t[o].s += k;
if (L == R) return (void)(t[o].ls = t[o].rs = t[o].s ? pre[L] : 0);
int M = (L + R) >> 1;
if (x <= M) ins(t[o].lc, L, M, x, k);
else ins(t[o].rc, M + 1, R, x, k);
pushup(o);
}
inline int merge(int o, int p) {
if (!o || !p) return o ^ p;
t[o].lc = merge(t[o].lc, t[p].lc);
t[o].rc = merge(t[o].rc, t[p].rc);
if (t[o].lc || t[o].rc) pushup(o);
else t[o].s = t[o].s + t[p].s, t[o].ls = t[o].rs = t[o].s ? t[o].ls | t[p].ls : 0;
return o;
}
inline void debug(int o, int L, int R) {
// dbg("o = %d, L = %d, R = %d, t[o].lc = %d, t[o].rc = %d, t[o].ls = %d, t[o].rs = %d, t[o].val = %d, t[o].s = %d\n", o, L, R, t[o].lc, t[o].rc, t[o].ls, t[o].rs, t[o].val, t[o].s);
assert(t[o].s >= 0);
assert((!!t[o].s) == !!(t[o].ls));
if (L == R) return;
int M = (L + R) >> 1;
debug(t[o].lc, L, M);
debug(t[o].rc, M + 1, R);
} inline void dfs2(int x, int fa = 0) {
for fec(i, x, y) if (y != fa) dfs2(y, x), rt[x] = merge(rt[x], rt[y]);
ans += (t[rt[x]].val + dist(t[rt[x]].ls, t[rt[x]].rs)) / 2;
// dbg("****** x = %d, ls = %d, rs = %d, dif = %d, %d, %d\n", x, t[rt[x]].ls, t[rt[x]].rs, (t[rt[x]].val + dist(t[rt[x]].ls, t[rt[x]].rs)) / 2, t[rt[x]].val, dist(t[rt[x]].ls, t[rt[x]].rs));
// debug(rt[x], 1, n);
assert(!((t[rt[x]].val + dist(t[rt[x]].ls, t[rt[x]].rs)) & 1));
} inline void work() {
dfs2(1);
printf("%lld\n", ans / 2);
} inline void init() {
read(n), read(m);
int x, y;
for (int i = 1; i < n; ++i) read(x), read(y), adde(x, y);
dfs1(1), rmq_init();
// for (int i = 1; i <= n; ++i) dbg("i = %d, dfn[i] = %d, dfn2[i] = %d\n", i, dfn[i], dfn2[i]);
for (int i = 1; i <= m; ++i) {
int x, y, p;
read(x), read(y);
p = lca(x, y);
// dbg("x = %d, y = %d, p = %d\n", x, y, p);
ins(rt[x], 1, n, dfn[y], 1), ins(rt[x], 1, n, dfn[x], 1);
ins(rt[y], 1, n, dfn[x], 1), ins(rt[y], 1, n, dfn[y], 1);
if (f[p]) ins(rt[f[p]], 1, n, dfn[x], -2), ins(rt[f[p]], 1, n, dfn[y], -2);
}
// dbg("****************** %d\n", lc[1][1]);
} int main() {
#ifdef hzhkk
freopen("hkk.in", "r", stdin);
#endif
init();
work();
fclose(stdin), fclose(stdout);
return 0;
}

bzoj5518 & loj3046 「ZJOI2019」语言 线段树合并+树链的并的更多相关文章

  1. 【LOJ】#3046. 「ZJOI2019」语言

    LOJ#3046. 「ZJOI2019」语言 先orz zsy吧 有一个\(n\log^3n\)的做法是把树链剖分后,形成logn个区间,这些区间两两搭配可以获得一个矩形,求矩形面积并 然后就是对于一 ...

  2. 「ZJOI2019」语言 解题报告

    「ZJOI2019」语言 3个\(\log\)做法比较简单,但是写起来还是有点麻烦的. 大概就是树剖把链划分为\(\log\)段,然后任意两段可以组成一个矩形,就是个矩形面积并,听说卡卡就过去了. 好 ...

  3. @loj - 3046@「ZJOI2019」语言

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 九条可怜是一个喜欢规律的女孩子.按照规律,第二题应该是一道和数据 ...

  4. 「ZJOI2019」语言

    传送门 Description 给定一棵\(n\)个点的树和\(m\)条链,两个点可以联会当且仅当它们同在某一条链上,求可以联会的点的方案数 \(n,m\leq10^5\) Solution  考虑计 ...

  5. 【线段树 树链剖分 差分 经典技巧】loj#3046. 「ZJOI2019」语言【未完】

    还是来致敬一下那过往吧 题目分析 先丢代码 #include<bits/stdc++.h> ; ; ; struct node { int top,son,fa,tot; }a[maxn] ...

  6. 【LOJ】#3043. 「ZJOI2019」线段树

    LOJ#3043. 「ZJOI2019」线段树 计数转期望的一道好题-- 每个点设两个变量\(p,q\)表示这个点有\(p\)的概率有标记,有\(q\)的概率到祖先的路径上有个标记 被覆盖的点$0.5 ...

  7. 「ZJOI2019」线段树 解题报告

    「ZJOI2019」线段树 听说有人喷这个题简单,然后我就跑去做,然后自闭感++,rp++(雾) 理性分析一波,可以发现最后形成的\(2^k\)个线段树,对应的操作的一个子集,按时间顺序作用到这颗线段 ...

  8. loj#2255. 「SNOI2017」炸弹 线段树优化建图,拓扑,缩点

    loj#2255. 「SNOI2017」炸弹 线段树优化建图,拓扑,缩点 链接 loj 思路 用交错关系建出图来,发现可以直接缩点,拓扑统计. 完了吗,不,瓶颈在于边数太多了,线段树优化建图. 细节 ...

  9. 「ZJOI2019」&「十二省联考 2019」题解索引

    「ZJOI2019」&「十二省联考 2019」题解索引 「ZJOI2019」 「ZJOI2019」线段树 「ZJOI2019」Minimax 搜索 「十二省联考 2019」 「十二省联考 20 ...

随机推荐

  1. 前端学习记录 week 1

    前端学习记录 week 1 基础知识 CSS盒模型 所有HTML元素可以看作盒子,在CSS中,"box model"这一术语是用来设计和布局时使用.CSS盒模型本质上是一个盒子,封 ...

  2. flex几种多列布局

    基本的等分三列布局 .container{ display: flex; width: 500px; height: 200px; } .left{ flex:1; background: red; ...

  3. 快速构建Spring Cloud工程

    spring cloud简介 spring cloud为开发人员提供了快速构建分布式系统的一些工具,包括配置管理.服务发现.断路器.路由.微代理.事件总线.全局锁.决策竞选.分布式会话等等.它运行环境 ...

  4. ASYNC_NETWORK_IO等待事件和调优

    测试反应测试数据库整体出现hang的情况,检查对应的等待事件,发现大量的resource_semaphore等待事件, 查看内存占用情况: ​SELECT  *  FROM    sys.dm_exe ...

  5. git使用,Git的skil-map,git配置http/https/socks5代理

    . 检出.克隆库: git clone git://git.openwrt.org/openwrt.git 2. git查看某个文件的修改历史 git log --pretty=oneline 文件名 ...

  6. The Linux usage model for device tree data

    Linux and the Device Tree The Linux usage model for device tree data Author: Grant Likely grant.like ...

  7. Sails.js中文文档,Sails中文文档

    Sails.js中文文档   http://sailsdoc.swift.ren/ Sails.js是一个Web框架,可以于轻松构建自定义,企业级Node.js Apps.它在设计上类似于像Ruby ...

  8. rpm安装软件时提示warning: *.rpm: Header V3 RSA/SHA256 Signature, keykey ID c105b9de

    在RedHat下有时候用rpm安装软件是会出现下面则中错误 1.安装时提示:warning: *.rpm: Header V3 RSA/SHA256 Signature, keykey ID c105 ...

  9. clientdataset 读取excel 如果excel 文件不存在的时候 相应的gird 会不显示数据, 鼠标掠过 gird 格子 才会显示数据。 这是一个bug 哈哈

    clientdataset 读取excel   如果excel 文件不存在的时候   相应的gird 会不显示数据, 鼠标掠过 gird 格子 才会显示数据.   这是一个bug 哈哈

  10. 字符串中的TRIM操作

    std::string& ltrim(std::string& str, const std::string& chars = "\t\n\v\f\r ") ...