P4606 [SDOI2018]战略游戏
圆方树的定义
圆方树是用来解决仙人掌图的问题的,那什么是仙人掌图呢?
即不存在边同时属于多个环的无向连通图是一棵仙人掌。
点双连通分量的定义
要介绍圆方树,首先要介绍点双连通分量。
一个点双连通图的一个定义是:图中任意两不同点之间都有至少两条点不重复的路径。
一种简单的定义:不存在割点的图。
但这种定义对于两点一边的图时是没用的,它没有割点,但是并不能找到两条不相交的路径,因为只有一条路径。(也可以理解为那一条路径可以算两次,但的确没有相交,因为不经过其他点)。
在点双连通图内,一个点可能属于多个点双,但是一条边属于恰好一个点双。
更多关于有向图的强连通分量的知识,请看我的博客 \(\to\) 强连通分量
更多关于点双连通分量的知识,请看我的博客 \(\to\) 双连通分量
继续介绍圆方树
关于圆方树的建图,也比较简单,将一个点双连通分量内的所有边删去,再将一个点双连通分量中的每个点向一个新建的点连边,这个新建的点即是方点。
所以在圆方树中有 \(n+c\) 个点,其中 \(n\) 是原图点数,\(c\) 是原图点双连通分量的个数。
每个点双都可以形成一个菊花图,多个菊花图通过原图中的割点连接在一起(因为点双的分隔点是割点)。
显然,圆方树中每条边连接一个圆点和一个方点。
在下面这张图中,\([1,2,3,4,5]\) 是圆点,\([6,7]\) 是方点。
而如果圆方树连通,则有以下性质:
-
方点之间不会存在连边。
-
原图的割点就是圆方树中度数大于 \(1\) 的圆点。
-
圆方数是一棵非常好的树,即点数等于边数加 \(1\)。
-
圆方树上任意一条路径上圆点方点间隔分布。
-
如果圆点的 \(size\) 为 \(1\),那么一个圆点子树的 \(size\) 和就是它下面的所有点的数量。
-
对于一个点双中的两点,它们之间简单路径的并集,恰好完全等于这个点双,即同一个点双中的两不同点 \(u\),\(v\) 之间一定存在一条简单路径经过给定的在同一个点双内的另一点 \(w\)。也就是说,考虑两圆点在圆方树上的路径,与路径上经过的方点相邻的圆点的集合,就等于原图中两点简单路径上的点集。
如果原图中某个连通分量只有一个点,则需要具体情况具体分析,我们在后续讨论中不考虑孤立点。
注意一条边连接两个点的在这里不算点双。
广义圆方树
普通圆方树只能解决仙人掌图上的问题,而广义圆方树则可以将所有无向图转化为圆方树处理。
广义圆方树性质:圆点方点相间,不存在两个‘’相同形状‘’的点相连。
与圆方树不同的是,广义圆方树需要把一条边连接两个点也看成一个点双,原本两个圆点有一条边相连,现在在中间插入一个方点间隔开就好了。
可以参照这张图
圆方树的应用
题目大意
给出一个无向图,和 \(q\) 个询问,每次给出 \(s\) 个点,问存在几个点,使得这个点和他相连的边被去除后,这 \(s\) 个点中,至少存在一对点互不相通。
解题思路
首先考虑删掉哪些点才能使得图上原本连通的两点变为不连通。
显然删掉两点的简单路径中必经的割点可以使得图上原本连通的两点变为不连通。
而这在圆方树上对应的就是两点路径上的圆点。
于是,我们轻松的想到一个办法: 直接找出所有的圆点不就好了?
然而,我们的时间复杂度是过不去的。
那么,如何快速求出所有圆点呢? 不妨换一种思路。 对于一个圆方树,如果我们能找到其包含所有点的最小的连通块,然后将其减掉 \(s\),就是我们的圆点的数量。
例如这张图:
假设给出的 \(s\) 个点分别为:\(4、5、6、7\)。
则建完圆方树就变成这样:
图中没加深的点就是圆方树中的方点。
易得,使用 Tarjan
算法建出圆方树,然后答案就是圆方树上包含所有关键点的最少点数联通块的圆点数量减去关键点的数量。
为了方便,我们设圆点的权值设为 \(1\) ,方点的权值为 \(0\) ,将点权放到这个点与其父节点的边上。
然后画一个图,发现,如果由 dfs
序从小到大,以此走过所有的点,然后再从第 \(s\) 个点走回第 \(1\) 个点。
在走过路径中,如果不考虑每相邻两个点的 LCA
(此时我们走的是树上最短路径,显然会经过 LCA
,这里说的不考虑就是不把它计入在内),每个点恰好被走了两次,而这些被走过的点恰好就是我们要求的联通块。
不过这样会有一个问题,就是第一个点和第 \(s\) 个点的 LCA
会不被统计,所以如果这个点是个圆点答案就再加 \(1\)。
AC CODE
#include <bits/stdc++.h>
using namespace std;
const int _ = 100005;
int n, m, q, cnt;
vector<int> G[_], T[_ * 2];
int dfn[_ * 2], low[_], cnt_node;
stack<int> s;
void Tarjan(int u)
{
low[u] = dfn[u] = ++cnt_node;
s.push(u);
for (auto v : G[u])
{
if (!dfn[v])
{
Tarjan(v);
low[u] = min(low[u], low[v]);
if (low[v] == dfn[u])
{
++cnt;
while (1)
{
int x = s.top();
s.pop();
T[cnt].push_back(x);
T[x].push_back(cnt);
// cout << cnt << " " << x << endl;
if (x == v)
break;
}
T[cnt].push_back(u);
T[u].push_back(cnt);
// cout << cnt << " " << u << endl;
}
}
else
low[u] = min(low[u], dfn[v]);
}
}
int dep[_ * 2], fa[_ * 2][18], dis[_ * 2];
void dfs(int u, int fz)
{
dfn[u] = ++cnt_node;
dep[u] = dep[fa[u][0] = fz] + 1;
dis[u] = dis[fz] + (u <= n);
for (int j = 0; j < 17; ++j)
fa[u][j + 1] = fa[fa[u][j]][j];
for (auto v : T[u])
if (v != fz)
dfs(v, u);
}
int LCA(int x, int y)
{
if (dep[x] < dep[y])
swap(x, y);
for (int j = 0, d = dep[x] - dep[y]; d; ++j, d >>= 1)
if (d & 1)
x = fa[x][j];
if (x == y)
return x;
for (int j = 17; ~j; --j)
if (fa[x][j] != fa[y][j])
x = fa[x][j], y = fa[y][j];
return fa[x][0];
}
int main()
{
int TT;
scanf("%d", &TT);
while (TT--)
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
{
G[i].clear();
dfn[i] = low[i] = 0;
}
for (int i = 1; i <= n * 2; ++i)
T[i].clear();
for (int i = 1, x, y; i <= m; ++i)
{
scanf("%d%d", &x, &y);
G[x].push_back(y);
G[y].push_back(x);
}
cnt = n;
cnt_node = 0;
Tarjan(1);
cnt_node = 0;
dfs(1, 0);
scanf("%d", &q);
while (q--)
{
int S, A[_];
scanf("%d", &S);
int Ans = -2 * S;
for (int i = 1; i <= S; ++i)
scanf("%d", &A[i]);
sort(A + 1, A + S + 1, [](int i, int j)
{ return dfn[i] < dfn[j]; });
for (int i = 1; i <= S; ++i)
{
int u = A[i], v = A[i % S + 1];
Ans += dis[u] + dis[v] - 2 * dis[LCA(u, v)];
}
if (LCA(A[1], A[S]) <= n)
Ans += 2;
printf("%d\n", Ans / 2);
}
}
return 0;
}
本文来自博客园,作者:蒟蒻orz,转载请注明原文链接:https://www.cnblogs.com/orzz/p/15154207.html
原文地址:https://www.cnblogs.com/orzz/p/15154207.html
- 【专业技术】android 应用程序如何获取root权限
- Nginx+Keepalived(双机热备)搭建高可用负载均衡环境(HA)
- SpringMVC+MongoDB+Maven整合(微信回调Oauth授权)
- ZeroClipboard实现多个浏览器兼容的复制文本到剪贴板的功能
- Shiro 权限框架使用总结
- Apriori算法介绍(Python实现)
- linux学习第六十二篇:添加自定义监控项目,配置邮件告警,测试告警,不发邮件的问题处理
- Entity Framework Core 2.0 入门
- Nodejs开发框架Express3.0开发手记–从零开始
- 使用 nvm 管理不同版本的 node 与 npm
- svg矢量图绘制以及转换为Android可用的VectorDrawable资源
- CListCtrl控件使用方法总结
- JavaScript基础考核真题——你能全做对吗?
- 拉手网面试题,不一样的难度
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Laravel ORM 数据model操作教程
- Laravel框架基础语法与知识点整理【模板变量、输出、include引入子视图等】
- laravel 解决Eloquent ORM的save方法无法插入数据的问题
- laravel框架中控制器的创建和使用方法分析
- php 使用expat方式解析xml文件操作示例
- laravel利用中间件做防非法登录和权限控制示例
- laravel框架中表单请求类型和CSRF防护实例分析
- Yii框架getter与setter方法功能与用法分析
- laravel框架中视图的基本使用方法分析
- laravel5 Eloquent 实现事务方式
- Laravel 微信小程序后端搭建步骤详解
- Laravel使用swoole实现websocket主动消息推送的方法介绍
- Laravel框架Eloquent ORM删除数据操作示例
- PHP常用函数之base64图片上传功能详解
- laravel-admin 实现在指定的相册下添加照片