题目传送门

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. Mybatis, 实现一对多

    我这里是拿商品做为例子 不多说直接上代码 Mapper.xml <?xml version="1.0" encoding="UTF-8"?> < ...

  2. 冲刺周五——Fifth Day

    #一.Fifth Day照片 #二.今日份燃尽图 #三.项目进展 * 码云团队协同环境构建完毕 * 利用Leangoo制作任务分工及生成燃尽图 * 完成AES加解密部分代码 * 用代码实现对文件的新建 ...

  3. (转)grep命令

    1.作用Linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来.grep全称是Global Regular Expression Print,表示全局 ...

  4. JVM(2) JVM内存模型

    一.概述 Java的内存管理采用[自动内存管理]机制,因为这个自动管理机制,Java程序员就不需要去写释放内存的代码,而且不容易出现内存泄漏问题(比C/C++程序员少一些烦恼).但是由于内存的申请和释 ...

  5. 初窥AST

    一个简单的AST示例: AST结构: 里面有program.name.loc.type.comments.tokens 先看program: 重点关注program里面的body这个数组: JS引擎中 ...

  6. 深入理解webpack(二) webpack-dev-server基本配置

     摘要:webpack-dev-server是一个使用了express的Http服务器,它的作用主要是为了监听资源文件的改变,该http服务器和client使用了websocket通信协议,只要资源文 ...

  7. 微信企业号 发送信息 shell

    微信企业号发送信息shell #可作为shell函数模块调用,用于微信通知.jenkins发版微信通知等等 # 微信API官方文档 https://work.weixin.qq.com/api/doc ...

  8. Ultra.Base

    winform 基础类库 https://github.com/ZixiangBoy/Ultra.Base

  9. idea下载和设置自动翻译(有道)

    1:下载 点击file,点击settings,找到plugins,之后所搜translation并下载,他会自动从新启动idea 2:设置translation 3:这个应用ID和秘钥需要在有道智云去 ...

  10. 应用安全 - PHPCMS - Joomla漏洞汇总

    Joomla 反序列化(版本低于3.4.5) CVE-2015-8562 RCE Date:October, 2019原理:https://blog.hacktivesecurity.com/inde ...