先让我们探索一下两条非树边以及树边能构成简单环的条件是什么,你会发现将第一条非树边的两个点在树上形成的链记为 \(W_1\),另一条即为 \(W_2\),那么当且仅当 \(W_1, W_2\) 有交时才能满足条件。因为当 \(W_1, W_2\) 没交时,那么中间一定会经过一些树边不被这两条链覆盖,但因为点只能走一次因此我们就不能回来了,所以这种情况下是不行的;而当 \(W_1, W_2\) 有交时很容易就可以构造出一条合法的简单环。

于是现在问题转化成统计有多少对链 \(W_1, W_2\) 满足其有交。这种链交问题我们一般拆成左右两条直上直下的链,这样可以减少讨论。令 \(W_1, W_2\) 中链顶更高的为 \(W_1\),不难发现两条链有交当且仅当 \(W_1\) 会穿过 \(W_2\) 的链顶与其链上儿子中间组成的这条边,因此对于每条链 \(W_i\) 我们在其链顶与其在链上的儿子组成的边上权值 \(+1\),那么和每条链交的链的数量就是其在链上的边上的权值之和,这个我们可以直接倍增计算得出,这部分复杂度 \(O(n \log n)\)。于此同时你会发现,如果两条链链顶不同,这样只会计算一次链交,如果两条链顶相同,这样会计算两次,因此我们还需要将这部分多余的减去。

不难发现我们将所有链挂在链顶上,那么链顶相同且有交的链当且仅当是都经过了这个链顶的一个儿子的这样一对链。于是我们把经过每个儿子的链的数量统计出来,令第 \(i\) 个儿子的数量为 \(c_i\),则多算的链数应该是:\(\dbinom{c_i}{2}\),减去即可,这部分复杂度 \(O(n)\)。

但是回过头来会发现一个问题,有没有可能两条链在左边边交一次右边边也交一次呢,这样就记重了。事实上是有的,当且仅当两条链的 \(LCA\) 相同并且两条链都相同地经过 \(LCA\) 的两个儿子。这部分记重我们只需要将所有链一样地挂在 \(LCA\) 上每次用 \(map\) 统计经过两个儿子的链的数量,令其中一种的数量为 \(x\),则多算的部分应该是:\(\dbinom{x}{2}\),减去即可。这部分复杂度 \(O(n \log n)\)。

一些坑点

  • 就算原来的链也是直上直下的也会计算重复,也需要减去算重的部分;于此同时需要注意第二次去重时不要统计经过其自身的和一个儿子的重复部分。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define rep(i, l, r) for(int i = l; i <= r; ++i)
#define dep(i, l, r) for(int i = r; i >= l; --i)
#define Next(i, u) for(int i = h[u]; i; i = e[i].next)
const int N = 200000 + 5;
const int K = 20 + 5;
struct edge{
int v, next;
}e[N << 1];
struct node{
int u, v;
}a[N << 1];
long long ans;
int n, m, u, v, tot, cnt, Lca, h[N], dep[N], tmp[N], f[N][K], dp[N][K];
vector <node> G[N];
map <int, int> M[N];
int read(){
char c; int x = 0, f = 1;
c = getchar();
while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
void add(int u, int v){
e[++tot].v = v, e[tot].next = h[u], h[u] = tot;
e[++tot].v = u, e[tot].next = h[v], h[v] = tot;
}
void dfs(int u, int fa){
f[u][0] = fa, dep[u] = dep[fa] + 1;
Next(i, u) if(e[i].v != fa) dfs(e[i].v, u);
}
int LCA(int x, int y){
if(dep[x] < dep[y]) swap(x, y);
dep(i, 0, 20) if(dep[f[x][i]] >= dep[y]) x = f[x][i];
if(x == y) return x;
dep(i, 0, 20) if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
return f[x][0];
}
int find(int x, int y){
dep(i, 0, 20) if(dep[f[x][i]] > dep[y]) x = f[x][i];
return x;
}
int calc(int x, int y){
int ans = 0;
dep(i, 0, 20) if(dep[f[x][i]] >= dep[y]) ans += dp[x][i], x = f[x][i];
return ans;
}
signed main(){
n = read(), m = read();
rep(i, 1, n - 1) u = read(), v = read(), add(u, v);
dfs(1, 0);
rep(j, 1, 20) rep(i, 1, n) f[i][j] = f[f[i][j - 1]][j - 1];
rep(i, n, m){
u = read(), v = read(), Lca = LCA(u, v); if(dep[u] < dep[v]) swap(u, v);
G[Lca].push_back((node){find(u, Lca), find(v, Lca)});
if(v != Lca) a[++cnt] = (node){u, Lca}, a[++cnt] = (node){v, Lca};
else a[++cnt] = (node){u, v};
}
rep(i, 1, n){
if(!G[i].size()) continue;
for(int j = 0; j < G[i].size(); ++j){
if(G[i][j].u > G[i][j].v) swap(G[i][j].u, G[i][j].v);
if(G[i][j].u != i && G[i][j].v != i) ans -= M[G[i][j].u][G[i][j].v];
++tmp[G[i][j].u], ++tmp[G[i][j].v], ++M[G[i][j].u][G[i][j].v];
}
Next(j, i) ans -= 1ll * tmp[e[j].v] * (tmp[e[j].v] - 1) / 2;
for(int j = 0; j < G[i].size(); ++j) --tmp[G[i][j].u], --tmp[G[i][j].v], --M[G[i][j].u][G[i][j].v];
}
rep(i, 1, cnt) ++dp[find(a[i].u, a[i].v)][0];
rep(j, 1, 20) rep(i, 1, n) dp[i][j] = dp[i][j - 1] + dp[f[i][j - 1]][j - 1];
rep(i, 1, cnt) ans += calc(a[i].u, a[i].v) - 1;
printf("%lld", ans);
return 0;
}

值得一提的时其实在第一次计算时不需要倍增去统计,我们直接利用差分的技巧统计链底到根的和减去链顶到根的和即可。

[USACO19JAN]Exercise Route P的更多相关文章

  1. [USACO19JAN]Exercise Route

    题目 这题的数据有点水,暴力合并\(set\)好像过了 分析一下这个题的性质,发现我们一条非树边就会形成一个环,而我们要求选择两个非树边,就会形成两个环,要求不走重复的点,就是说我们需要走一个大环,且 ...

  2. [USACO18DEC]The Cow Gathering P

    首先可以思考一下每次能删去的点有什么性质. 不难发现,每次能删去的点都是入度恰好为 \(1\) 的那些点(包括 \(a_i \rightarrow b_i\) 的有向边). 换句话说,每次能删去的点既 ...

  3. Application Request Route实现IIS Server Farms集群负载详解

    序言 随着公司业务的发展,后台业务就变的越来越多,然而服务器的故障又像月经一样,时不时的汹涌而至,让我们防不胜防.那么后台的高可用,以及服务器的处理能力就要做一个横向扩展的方案,以使后台业务持续的稳定 ...

  4. .net core 源码解析-mvc route的注册,激活,调用流程(三)

    .net core mvc route的注册,激活,调用流程 mvc的入口是route,当前请求的url匹配到合适的route之后,mvc根据route所指定的controller和action激活c ...

  5. angular路由——ui.route

    angular路由 使用案例 <!DOCTYPE html> <html lang="en"> <head> <meta charset= ...

  6. 如何在ARM中创建Express Route

    很早之前就想试试Azure的express route,但是一直没有找到合适的机会,正好有个客户需要上express route,所以最近先自己研究研究,防止在做poc的时候耗费更多时间,本次场景我们 ...

  7. Python flask @app.route

    转载自 http://python.jobbole.com/80956/ 下面是Flask主页给我们的第一个例子,我们现在就由它入手,深入理解“@app.route()”是如何工作的.         ...

  8. MIT 6.828 JOS学习笔记12 Exercise 1.9

    Lab 1中Exercise 9的解答报告 Exercise 1.9: 判断一下操作系统内核是从哪条指令开始初始化它的堆栈空间的,以及这个堆栈坐落在内存的哪个地方?内核是如何给它的堆栈保留一块内存空间 ...

  9. MIT 6.828 JOS学习笔记13 Exercise 1.10

    Lab 1 Exercise 10 为了能够更好的了解在x86上的C程序调用过程的细节,我们首先找到在obj/kern/kern.asm中test_backtrace子程序的地址, 设置断点,并且探讨 ...

随机推荐

  1. [opencv]KAZE、AKAZE特征检测、匹配与对象查找

    AkAZE是KAZE的加速版 与SIFT,SUFR比较: 1.更加稳定 2.非线性尺度空间 3.AKAZE速度更加快 4.比较新的算法,只有Opencv新的版本才可以用 AKAZE局部匹配介绍 1.A ...

  2. Java高级程序设计笔记 • 【第2章 多线程(一)】

    全部章节   >>>> 本章目录 2.1 线程的概述 2.1.1 进程 2.1.2 多线程优势 2.1.3 Thread 类 2.1.4 实践练习 2.2 Runnable接口 ...

  3. Google面试评分卡

    Google对工程面试之前,会让面试人员填一张评分卡,以加强面试官对你的理解,大致内容如下: 0 - 对于相关技术领域还不熟悉. 1 - 可以读懂这个领域的基础知识. 2 - 可以实现一些小的改动,清 ...

  4. 深入浏览器工作原理和JS引擎(V8引擎为例)

    浏览器工作原理和JS引擎 1.浏览器工作原理 在浏览器中输入查找内容,浏览器是怎样将页面加载出来的?以及JavaScript代码在浏览器中是如何被执行的? 大概流程可观察以下图: 首先,用户在浏览器搜 ...

  5. RazorEngine.NetCore 相见恨晚,它让我彻底放弃了T4模板

    在dbfrist 时代,用T4模板生成代码,貌似还没有感觉到别扭.但是到了codefrist 后,我想要实体生成生成备注,我就得想方设法的去把备注弄到数据库,然后 还要处理模型中类型像枚举这种属性,渐 ...

  6. 初识python 之 smtplib 发送(dolphinscheduler任务监测)邮件

    需求 监测dolphinscheduler调度系统,任务执行异常情况.如有异常,则发送邮件通知. 处理思路 因DS本身自带的邮件发送功能,不能正常发送邮件. 故而,通过查询DS源数据表,获取当前任务执 ...

  7. Shell 中的 expect 命令

    目录 expect 介绍 expect 安装 expect 语法 自动拷贝文件到远程主机 示例一 示例二 示例三 示例四 expect 介绍 借助 expect 处理交互的命令,可以将交互过程如 ss ...

  8. js数组清空的两种方式

    编辑器加载中...方式1,length赋值为0 这种方式很有意思, 其它语言如Java,其数组的length是只读的,不能被赋值.如 int[] ary = {1,2,3,4}; ary.length ...

  9. 【Java】单例设计模式

    文章目录 单例设计模式 什么是设计模式 单例设计模式 实现 饿汉式 懒汉式 饿汉式与懒汉式的区别 饿汉式 懒汉式 单例模式的应用场景 单例设计模式 什么是设计模式 设计模式是在大量的实践中总结和理论化 ...

  10. RHCSA 第一天

    1.修改主机名: 查看主机名 2.查看日期 使用指定格式输出日期 YY-mm-DD HH:MM:SS 3.将"We are learning RHCSA"输出在屏幕上 4.使用ti ...