先让我们探索一下两条非树边以及树边能构成简单环的条件是什么,你会发现将第一条非树边的两个点在树上形成的链记为 \(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. matplotlib 高阶之Transformations Tutorial

    目录 Data coordinates Axes coordinates Blended transformations 混合坐标系统 plotting in physical units 使用off ...

  2. 后缀数组【原理+python代码】

    后缀数组 参考:https://blog.csdn.net/a1035719430/article/details/80217267 https://blog.csdn.net/YxuanwKeith ...

  3. [数据结构]严蔚敏版(C数据结构)配套实现程序111例

    以下为根据严蔚敏版数据结构的示例和概念实现的程序 目录 一.严蔚敏版(C数据结构)配套实现程序111例 1.数组与字符串 2.栈与队列 3.链表LinkList 4.树与二叉树 5.排序相关算法 6. ...

  4. Android 摄像头预览悬浮窗

    用CameraX打开摄像头预览,显示在界面上.结合悬浮窗的功能.实现一个可拖动悬浮窗,实时预览摄像头的例子. 这个例子放进了单独的模块里.使用时注意gradle里的细微差别. 操作摄像头,打开预览.这 ...

  5. CSS基础 实战案例 模拟小米官方导航栏

    效果图 html结构 <ul> <li><a href="#">Xiaomi手机</a></li> <li> ...

  6. 通过 v-once 创建低开销的静态组件

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <script s ...

  7. 初识python:多线程

    多线程:在一个程序中,独立运行的程序片断叫作"线程"(Thread),利用它编程的概念就叫作"多线程处理".即:一个进程中,多个线程. 举个例说明:就像是一列火 ...

  8. android studio 获取 SHA1 值

    1. 生成密钥文件 2.找到控制台 输入指令 cd c: cd C:\Users\[当前登录的用户文件夹]\.android 如 cd C:\Users\cenxi\.android 就是这里 然后输 ...

  9. 正则验证&模态框

    在日常生活中,凡是需要表单验证的都会用到正则验证.下面拿一个简单的带有模态框的正则验证的小demo看一下     <style>         /* 遮罩层 */         .ma ...

  10. 局域网内部怎么安全接入U盘?

    准备工具: 内部专用U盘一个: 能连接外网的电脑(暂称"安全机")一个. 第一.安全机上安装360杀毒.360安全卫士或其它安全软件.并经常更新病毒库.木马库. 第二.外来U盘先通 ...