@description@

九条可怜是一个喜欢规律的女孩子。按照规律,第二题应该是一道和数据结构有关的题。

在一个遥远的国度,有 n 个城市。城市之间有 n - 1 条双向道路,这些道路保证了任何两个城市之间都能直接或者间接地到达。

在上古时代,这 n 个城市之间处于战争状态。在高度闭塞的环境中,每个城市都发展出了自己的语言。而在王国统一之后,语言不通给王国的发展带来了极大的阻碍。为了改善这种情况,国王下令设计了 m 种通用语,并进行了 m 次语言统一工作。在第 i 次统一工作中,一名大臣从城市 si 出发,沿着最短的路径走到了 ti,教会了沿途所有城市(包括 si, ti)使用第 i 个通用语。

一旦有了共通的语言,那么城市之间就可以开展贸易活动了。两个城市 ui, vi 之间可以开展贸易活动当且仅当存在一种通用语 L 满足 ui 到 vi 最短路上的所有城市(包括 ui, vi),都会使用 L。

为了衡量语言统一工作的效果,国王想让你计算有多少对城市 (u, v) (u < v),他们之间可以开展贸易活动。

原题传送门。

@solution@

分为 u, v 有祖先关系;u, v 无祖先关系两类统计。

有祖先关系,不妨假设 u 是 v 的祖先。

只需求出路径的某一端在 v 的子树中,向上延伸深度最小为多少。

深度最小就是 lca,自下而上更新即可。

无祖先关系,不妨假设 dfs 序中 u 在 v 前面。

一样的,路径的某一端在 v 的子树,此时路径另一端 dfs 序中需要在 v 前面。

但是不同的路径可能会重复经过某一个点,导致重复统计。

假设所有路径另一端点的点集为 S,我们取 S + {v} 到根的链的并集,然后扣掉 {v} 到根的点数,就可以得到答案。

链并集就是个经典问题:∑端点深度 - ∑dfs序中相邻点lca的深度。然后可以用线段树合并维护链并集。

如果用 O(logn) 的 lca,则总时间复杂度为 O(nlog^2n)。

当然还要在线段树中去掉 dfs 序在 v 后面的点。

@accepted code@

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std; typedef long long ll; const int MAXN = 100000; struct edge{
int to; edge *nxt;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt = edges;
void addedge(int u, int v) {
edge *p = (++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p;
p = (++ecnt);
p->to = u, p->nxt = adj[v], adj[v] = p;
} int fa[20][MAXN + 5], dep[MAXN + 5];
int dfn[MAXN + 5], tid[MAXN + 5], dcnt;
void dfs1(int x, int f) {
fa[0][x] = f;
for(int i=1;i<20;i++)
fa[i][x] = fa[i-1][fa[i-1][x]];
dep[x] = dep[f] + 1, dfn[++dcnt] = x, tid[x] = dcnt;
for(edge *p=adj[x];p;p=p->nxt)
if( p->to != f ) dfs1(p->to, x);
}
int lca(int u, int v) {
if( dep[u] < dep[v] ) swap(u, v);
for(int i=19;i>=0;i--)
if( dep[fa[i][u]] >= dep[v] )
u = fa[i][u];
if( u == v ) return u;
for(int i=19;i>=0;i--)
if( fa[i][u] != fa[i][v] )
u = fa[i][u], v = fa[i][v];
return fa[0][u];
} int ch[2][20*MAXN + 5], lm[20*MAXN + 5], rm[20*MAXN + 5], ncnt;
ll sum[20*MAXN + 5]; int rt[20*MAXN + 5]; void pushup(int x) {
lm[x] = (lm[ch[0][x]] != -1 ? lm[ch[0][x]] : lm[ch[1][x]]);
rm[x] = (rm[ch[1][x]] != -1 ? rm[ch[1][x]] : rm[ch[0][x]]);
sum[x] = sum[ch[0][x]] + sum[ch[1][x]];
if( rm[ch[0][x]] != -1 && lm[ch[1][x]] != -1 )
sum[x] -= dep[lca(dfn[rm[ch[0][x]]], dfn[lm[ch[1][x]]])];
}
void update(int &x, int l, int r, int p, int d) {
if( !x ) x = (++ncnt), lm[x] = -1, rm[x] = -1;
if( l == r ) {
if( d == 1 ) {
if( lm[x] == -1 )
lm[x] = rm[x] = l, sum[x] = dep[dfn[l]];
} else if( d == -1 ) {
if( lm[x] != -1 )
lm[x] = rm[x] = -1, sum[x] = 0;
}
return ;
}
int m = (l + r) >> 1;
if( p <= m ) update(ch[0][x], l, m, p, d);
else update(ch[1][x], m + 1, r, p, d);
pushup(x);
}
int merge(int x, int y, int l, int r) {
if( !x || !y ) return x + y;
if( l == r ) {
if( lm[y] != -1 )
lm[x] = rm[x] = l, sum[x] = dep[dfn[l]];
return x;
}
int m = (l + r) >> 1;
ch[0][x] = merge(ch[0][x], ch[0][y], l, m);
ch[1][x] = merge(ch[1][x], ch[1][y], m + 1, r);
pushup(x); return x;
} int n, m;
vector<int>v[MAXN + 5]; int mnd[MAXN + 5]; ll ans;
void dfs2(int x, int f) {
rt[x] = 0;
for(int i=0;i<v[x].size();i++)
update(rt[x], 1, n, tid[v[x][i]], 1);
for(edge *p=adj[x];p;p=p->nxt) {
if( p->to == f ) continue;
dfs2(p->to, x), mnd[x] = min(mnd[x], mnd[p->to]);
rt[x] = merge(rt[x], rt[p->to], 1, n);
}
ans += (dep[x] - mnd[x]);
while( rm[rt[x]] >= tid[x] )
update(rt[x], 1, n, rm[rt[x]], -1);
ans += sum[rt[x]];
if( rm[rt[x]] != -1 ) ans -= dep[lca(x, dfn[rm[rt[x]]])];
}
int main() {
// freopen("language.in", "r", stdin);
// freopen("language.out", "w", stdout); scanf("%d%d", &n, &m);
for(int i=1,x,y;i<n;i++)
scanf("%d%d", &x, &y), addedge(x, y); dfs1(1, 0);
for(int i=1;i<=n;i++) mnd[i] = dep[i];
for(int i=1,s,t,l;i<=m;i++) {
scanf("%d%d", &s, &t), l = lca(s, t);
mnd[s] = min(mnd[s], dep[l]), mnd[t] = min(mnd[t], dep[l]);
if( tid[s] > tid[t] ) swap(s, t); v[t].push_back(s);
}
lm[0] = rm[0] = -1, dfs2(1, 0), printf("%lld\n", ans);
}

@details@

我竟然做出来一道ZJOI题?可能也只有这一道了吧

数组太多了可能会搞混,一定要区分清楚。

理论上最优复杂度是写 O(1) 的 lca,好在出题人没有卡(

@loj - 3046@「ZJOI2019」语言的更多相关文章

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

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

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

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

  3. Loj #3044. 「ZJOI2019」Minimax 搜索

    Loj #3044. 「ZJOI2019」Minimax 搜索 题目描述 九条可怜是一个喜欢玩游戏的女孩子.为了增强自己的游戏水平,她想要用理论的武器武装自己.这道题和著名的 Minimax 搜索有关 ...

  4. Loj #3045. 「ZJOI2019」开关

    Loj #3045. 「ZJOI2019」开关 题目描述 九条可怜是一个贪玩的女孩子. 这天,她和她的好朋友法海哥哥去玩密室逃脱.在他们面前的是 \(n\) 个开关,开始每个开关都是关闭的状态.要通过 ...

  5. Loj #3042. 「ZJOI2019」麻将

    Loj #3042. 「ZJOI2019」麻将 题目描述 九条可怜是一个热爱打麻将的女孩子.因此她出了一道和麻将相关的题目,希望这题不会让你对麻将的热爱消失殆尽. 今天,可怜想要打麻将,但是她的朋友们 ...

  6. 「ZJOI2019」语言 解题报告

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

  7. bzoj5518 & loj3046 「ZJOI2019」语言 线段树合并+树链的并

    题目传送门 https://loj.ac/problem/3046 题解 首先问题就是问有多少条路径是给定的几条路径中的一条的一个子段. 先考虑链的做法. 枚举右端点 \(i\),那么求出 \(j\) ...

  8. 「ZJOI2019」语言

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

  9. @loj - 3043@「ZJOI2019」线段树

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 九条可怜是一个喜欢数据结构的女孩子,在常见的数据结构中,可怜最喜 ...

随机推荐

  1. Windows系统下curl的下载和配置

    curl的下载和配置 简介:用URL规则在命令行下工作的文件传输工具. 下载:下载地址为 https://curl.haxx.se/download.html,在最底部找到Windows的版本,我下载 ...

  2. Python之日志处理(logging模块二实战)

    实战篇 import logging import logging.handlers LOG_PATH = r'./' def logConfig_1(): ''' 配置 log 输出到文件 : fi ...

  3. C#实现自定义列表

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  4. spark学习笔记总结

    Spark简介 spark 可以很容易和yarn结合,直接调用HDFS.Hbase上面的数据,和hadoop结合.配置很容易. spark发展迅猛,框架比hadoop更加灵活实用.减少了延时处理,提高 ...

  5. JavaScript几种继承方式的总结

    1.原型链继承 直接将子类型的原型指向父类型的实例,即"子类型.prototype = new 父类型();",实现方法如下: //父类构造函数 function father(n ...

  6. PHP获取今日、本周、本月、今年的开始日期和结束日期

    /** * 今天开始的Y-m-d H:i:s * * @return string */ public static function beginToday() { return date('Y-m- ...

  7. 透过 NestedScrollView 源码解析嵌套滑动原理

    NestedScrollView 是用于替代 ScrollView 来解决嵌套滑动过程中的滑动事件的冲突.作为开发者,你会发现很多地方会用到嵌套滑动的逻辑,比如下拉刷新页面,京东或者淘宝的各种商品页面 ...

  8. Postman学习笔记(一)

    一.简介 Postman是一种网页调试与发送网页 http 请求的 chrome 插件.我们可以用来很方便的 模拟 get 或者 post 或者其他方式的请求来调试接口. 二.安装 1.chrome浏 ...

  9. jchdl - RTL实例 - MOS6502 CPU

    https://mp.weixin.qq.com/s/OguQKMU64GGdinCJjgyeKw   实现MOS6502 CPU,主要是实现状态机.   参考链接 https://github.co ...

  10. Java实现 LeetCode 516 最长回文子序列

    516. 最长回文子序列 给定一个字符串s,找到其中最长的回文子序列.可以假设s的最大长度为1000. 示例 1: 输入: "bbbab" 输出: 4 一个可能的最长回文子序列为 ...