「IOI2017」西默夫 的一个另类做法
我们发现如果我们有一个环套树的话,那么我们可以把这个环套树去掉每一条环上的边\(e\),问一遍有多少御道在这棵树上。假设删去\(e\)后答案为\(A_e\)。
如果答案全部一样,那么说明环上的边都不在御道里面(不可能都在)。否则设答案有\(k\),\(k + 1\)两种。那么如果\(A_e = k\),那么\(e\)在,否则\(e\)就不在了。
我们先随便建一棵生成树,然后先试图确定每条树边是否在御道里面。我们依次考虑每一条非树边,如果加入这条非树边\(e\)后,边双连通分量个数变小了,那么我们考虑它让哪些桥消失。这些桥均在一个环上。那么我们把这个环中所有的桥边、\(e\)和恰好一条非桥边(如果存在的话)给询问一遍,就可以类似地得到\(e\)和所有桥边的状态了!最后所有桥边一定要被选,否则连通性会出问题。
注意到每一步的复杂度可以被边双连通分量的减少量bound住,所以这一步询问次数为\(O(n)\)。
之后我们把还未确定状态的那些边定向,方向为编号较大的点到编号较小的点。然后对每个点的出边,我们用分治的方法来求出每条边是否是御道。
这里我们考虑给定一个出边的子集,找到一棵生成树使得它恰好包含这些出边的子集,而不包含其它出边,且它包含的其它的边状态已知。那么我们询问这棵树,就可以得到有多少出边是御道了。分治大概就是说每次询问左边一半的个数,然后如果有的话就递归左边,类似地判断是否要递归右边。
由于御道个数为\(n - 1\),而对每个点分治的复杂度为这个点的出边的御道个数*\(\log n\),所以总询问次数为\(O(n \log n)\),可以通过此题!
代码如下:
#include "simurgh.h"
#include <bits/stdc++.h>
using namespace std;
const int N = 505, M = N * N;
int V, E, rt[N], col[N], s[M], t[M], ans[M];
vector<int> tree[N], vec;
bool intree[M];
int findroot (int u) {
return rt[u] == u ? u : rt[u] = findroot(rt[u]);
}
int find_tree () {
for (int i = 0; i < E; i++) {
int x = findroot(s[i]), y = findroot(t[i]);
if (x != y) {
intree[i] = true, rt[x] = y;
vec.push_back(i);
tree[s[i]].push_back(i);
tree[t[i]].push_back(i);
}
}
return count_common_roads(vec);
}
int par[N], path[N];
void dfs (int u, int fa) {
for (int i = 0; i < tree[u].size(); i++) {
int id = tree[u][i], v = s[id] == u ? t[id] : s[id];
if (v != fa) {
par[v] = u, path[v] = id;
dfs(v, u);
}
}
}
vector<int> get_route (int u, int v) {
vector<int> route;
dfs(u, -1);
for (int i = v; i != u; i = par[i]) {
if (ans[path[i]] < 0) route.push_back(path[i]);
}
return route;
}
int extra (int u, int v) {
for (int i = v; i != u; i = par[i]) {
if (ans[path[i]] >= 0) return path[i];
}
return -1;
}
void replace (int e1, int e2) {
for (int i = 0; i < vec.size(); i++) {
if (vec[i] == e1) vec[i] = e2;
}
}
void shrink (int u, int v) {
for (int i = v; i != u; i = par[i]) {
if (ans[path[i]] < 0) {
int c = col[i];
for (int j = 0; j < V; j++) {
if (col[j] == c) col[j] = col[u];
}
}
}
}
void query_tree () {
int total = find_tree();
for (int i = 0; i < E; i++) {
if (!intree[i] && col[s[i]] != col[t[i]]) {
vector<int> route = get_route(s[i], t[i]), res;
shrink(s[i], t[i]);
bool flag[3] = {false};
for (int j = 0; j < route.size(); j++) {
replace(route[j], i);
res.push_back(total - count_common_roads(vec));
flag[res[j] + 1] = true, replace(i, route[j]);
}
if (flag[0] || flag[2]) ans[i] = flag[0] ? 1 : 0;
else {
int id = extra(s[i], t[i]);
if (id) {
replace(id, i);
ans[i] = count_common_roads(vec) - total + ans[id];
replace(i, id);
}
else ans[i] = 0;
}
for (int j = 0; j < route.size(); j++) ans[route[j]] = ans[i] + res[j];
}
}
for (int i = 0; i < E; i++) {
if (intree[i] && ans[i] < 0) ans[i] = 1;
}
}
vector<int> g[N];
int query (int u, int l, int r) {
int num = 0;
vec.clear();
for (int i = 0; i < V; i++) rt[i] = i;
for (int i = l; i <= r; i++) {
int id = g[u][i], v = s[id] == u ? t[id] : s[id];
rt[findroot(u)] = rt[findroot(v)];
vec.push_back(id);
}
for (int i = 0; i < E; i++) {
if (intree[i]) {
int x = findroot(s[i]), y = findroot(t[i]);
if (x != y) {
vec.push_back(i);
rt[x] = y, num += ans[i];
}
}
}
return count_common_roads(vec) - num;
}
void solve (int u, int l, int r, int num) {
int mid = l + r >> 1;
if (l == r) ans[g[u][mid]] = (bool)num;
else {
int num_ = num ? query(u, l, mid) : 0;
solve(u, l, mid, num_), solve(u, mid + 1, r, num - num_);
}
}
vector<int> find_roads (int n, vector<int> u, vector<int> v) {
V = n, E = u.size();
for (int i = 0; i < V; i++) col[i] = rt[i] = i;
for (int i = 0; i < E; i++) {
ans[i] = -1, intree[i] = false;
s[i] = u[i], t[i] = v[i];
}
query_tree();
for (int i = 0; i < E; i++) {
if (ans[i] < 0) g[max(s[i], t[i])].push_back(i);
}
for (int i = 0; i < V; i++) {
if (!g[i].empty()) {
int num = query(i, 0, g[i].size() - 1);
solve(i, 0, g[i].size() - 1, num);
}
}
vector<int> tree;
for (int i = 0; i < E; i++) {
if (ans[i]) tree.push_back(i);
}
return tree;
}
「IOI2017」西默夫 的一个另类做法的更多相关文章
- 「IOI2017」接线 的另类做法
看到这题,我的第一反应是:这就是一个费用流模型?用模拟费用流的方法? 这应该是可以的,但是我忘记了怎么模拟费用流了IOI不可能考模拟费用流.于是我就想了另外一个方法. 首先我们考虑模拟费用流的模型如下 ...
- Linux 小知识翻译 - 「日志」(log)
这次聊聊「日志」. 「日志」主要指系统或者软件留下的「记录」.出自表示「航海日志」的「logbook」. 经常听说「出现问题的时候,或者程序没有安装自己预期的来运行的时候,请看看日志!」. 确实,记录 ...
- 「SCOI2016」妖怪 解题报告
「SCOI2016」妖怪 玄妙...盲猜一个结论,然后过了,事后一证,然后假了,数据真水 首先要最小化 \[ \max_{i=1}^n (1+k)x_i+(1+\frac{1}{k})y_i \] \ ...
- P4711 「化学」相对分子质量
P4711 「化学」相对分子质量 给你一个字符串让你输出相对分子质量(弱智字符串模拟) 我比赛tm调了两个半小时啊QAQ 希望以后能增加代码力吧,纪念挂代码 Code #include<iost ...
- 「NOI2018」冒泡排序
「NOI2018」冒泡排序 考虑冒泡排序中一个位置上的数向左移动的步数 \(Lstep\) 为左边比它大的数的个数,向右移动的步数 \(Rstep\) 为右边比它大的数的个数,如果 \(Lstep,R ...
- 「SDOI2005」区间
「SDOI2005」区间 传送门 记录每一个位置作为左端点和右端点的出现次数,然后直接考虑差分即可. 参考代码: #include <cstdio> #define rg register ...
- 「POJ3613」Cow Relays
「POJ3613」Cow Relays 传送门 就一个思想:\(N\) 遍 \(\text{Floyd}\) 求出经过 \(N\) 个点的最短路 看一眼数据范围,想到离散化+矩阵快速幂 代码: #in ...
- Python后端日常操作之在Django中「强行」使用MVVM设计模式
扫盲 首先带大家了解一下什么是MVVM模式: 什么是MVVM?MVVM是Model-View-ViewModel的缩写. MVVM是MVC的增强版,实质上和MVC没有本质区别,只是代码的位置变动而已 ...
- 10月1日之后,你新建的GitHub库默认分支不叫「master」了
从 2020 年 10 月 1 日开始,GitHub 上的所有新库都将用中性词「main」命名,取代原来的「master」,因为后者是一个容易让人联想到奴隶制的术语. 这个决定并不是最近才做出的.今年 ...
随机推荐
- Python_selenium案例:
selenium案例 #coding=utf-8 #select下拉框处理 from selenium import webdriver from selenium.webdriver.common. ...
- Nacos一致性算法
1. CAP原则 又称CAP定理,指的是在一个分布式系统中,一致性(Consistency).可用性(Availability).分区容错性(Partition tolerance).CAP 原则指的 ...
- day01-网络基础
一.知识点 1.socket.socket 创建一个 socket,该函数带有两个参数: Address Family:可以选择 AF_INET(用于 Internet 进程间通信) 或者 AF_UN ...
- Hadoop大数据平台之Zookeeper搭建
环境:CentOS 7.4 (1708 DVD) 工具:MobaXterm 1. 使用xftp将hadoop上传到/usr/local目录下,将其解压并重命名. 2. 修改环境变量并source. ...
- 实在是秒啊,我还从来没见过把Spring之AOP讲的这么通俗易懂的,安排!
Spring之AOP 什么是AOP? AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. ...
- Vegas实战——如何导入导出视频
Vegas作为一款专业的视频非编软件,在国内受到了很多用户的喜爱.小编认为,对于很多用户来说,他们选择sony vegas的一个原因是vegas在不论是从产品性能,还是使用效果上,都很容易被用户接受. ...
- CorelDRAW绘制的优秀人物肖像插画作品
艺术创作 关于作者 Dmitry Demidenko (LINEKING),1986 年出生于俄罗斯的斯帕斯克达利尼.他自幼痴迷于绘画,而且对矢量图形很有天赋.他从一家小型省立印刷公司的小设计师做起, ...
- 吉他弹唱上手——使用节奏变化弹好chord谱
本篇文章将向大家介绍如何改造来自网上的chord谱. 在各位日常的弹唱之中,应该会常常遇到朋友点歌的情况,如果点唱的这首歌我们听过,那我们尚可以靠以往的记忆来应付.如果这首歌我们只是曾经听到过听过,而 ...
- FL Studio中的音频剪辑功能讲解
音频剪辑,是FL Studio中的一个特色功能,音频剪辑的目的是保持在播放列表中显示和触发的音频,可以根据需要对它们进行切片和排列.但需要注意的是音频剪辑这个功能在FL Studio的基础版(果味版) ...
- H5系列之新input
虽说H5 新填了几个很方便的input 类型,但是吧,想法是美好的,但是现实很残酷,兼容性不太好.基本只有google浏览器能用.但是既然出了,那么就要了解他,知道有这么一个东西就好. input类型 ...