Codeforces 556D Restructuring Company
Even the most successful company can go through a crisis period when you have to make a hard decision — to restructure, discard and merge departments, fire employees and do other unpleasant stuff. Let's consider the following model of a company.
There are n people working for the Large Software Company. Each person belongs to some department. Initially, each person works on his own project in his own department (thus, each company initially consists ofn departments, one person in each).
However, harsh times have come to the company and the management had to hire a crisis manager who would rebuild the working process in order to boost efficiency. Let's use team(person) to represent a team where person person works. A crisis manager can make decisions of two types:
- Merge departments team(x) and team(y) into one large department containing all the employees ofteam(x) and team(y), where x and y (1 ≤ x, y ≤ n) — are numbers of two of some company employees. If team(x) matches team(y), then nothing happens.
- Merge departments team(x), team(x + 1), ..., team(y), where x and y (1 ≤ x ≤ y ≤ n) — the numbers of some two employees of the company.
At that the crisis manager can sometimes wonder whether employees x and y (1 ≤ x, y ≤ n) work at the same department.
Help the crisis manager and answer all of his queries.
The first line of the input contains two integers n and q (1 ≤ n ≤ 200 000, 1 ≤ q ≤ 500 000) — the number of the employees of the company and the number of queries the crisis manager has.
Next q lines contain the queries of the crisis manager. Each query looks like type x y, where
. If type = 1 or type = 2, then the query represents the decision of a crisis manager about merging departments of the first and second types respectively. If type = 3, then your task is to determine whether employees x and y work at the same department. Note that x can be equal to y in the query of any type.
For each question of type 3 print "YES" or "NO" (without the quotes), depending on whether the corresponding people work in the same department.
8 6
3 2 5
1 2 5
3 2 5
2 4 7
2 1 2
3 1 7
NO
YES
YES
这是一道很好的数据结构问题(我的看法)。
题意是:
$n$ 个元素编号为 $1$ 到 $n$,初始时这 $n$ 个元素各自处在一个(单元素)集合(singleton)中,要求支持下述三种操作
1 $x$, $y$ 将元素 $x$,$y$ 所在集合合并
2 $x$, $y$($y\ge x$)将元素 $x, x+1, \dots, y$ 所在集合合并
3 $x$, $y$ 查询 $x$、$y$ 是否在同一集合内
This problem allows a lot of solution with different time asymptotic. Let's describe a solution in
.
Let's first consider a problem with only queries of second and third type. It can be solved in the following manner. Consider a line consisting of all employees from 1 to n. An observation: any department looks like a contiguous segment of workers. Let's keep those segments in any logarithmic data structure like a balanced binary search tree (std::set or TreeSet). When merging departments from x to y, just extract all segments that are in the range [x, y] and merge them. For answering a query of the third type just check if employees x and y belong to the same segment. In such manner we get a solution of an easier problem in $O(\log n)$ per query.
Q1: 怎样用 std::set 在 $O(\log n)$ 的时间内将 $[x, y]$ 范围内的 segments 提取出来并且合并呢?
When adding the queries of a first type we, in fact, allow some segments to correspond to the same department. Let's add a DSU for handling equivalence classes of segments. Now the query of the first type is just using merge inside DSU for departments which x and y belong to. Also for queries of the second type it's important not to forget to call merge from all extracted segments.
So we get a solution in $ O(q(\log n + \alpha(n))) = O(q\log n)$ time.
正如题解所说,若果只考虑2,3两种操作,那么用线段树维护区间就可以了(当然还可以按别种方式维护,但我第一个想到的就是线段树)。我们来考虑这个extract all segments that are in the range [x, y] and merge them要怎么写。线段树的本质就是4个字——维护区间。维护区间做何用呢?答曰:查询任意区间I的某种信息 (information) INFO(I)或者也可称为区间I的某种性质(property)P(I)。概括起来就是通过维护有限个节点(区间)的某些信息从而支持对任意区间的某些信息的查询。线段树的查询就是个提取(extract)区间信息的过程:
Query(id, L, R, l, r)就是提取目标区间(l, r)与节点(L, R)的交集(max(l, L), min(r, R))的信息。如果(L, R)含有我们所需的关于(max(l, L), min(r, R))的信息,则直接返回这些信息,否则要向下分治。
//extract info. of target subsegment within node (L, R)
int extract(int id, int L, int R, int l, int r){
if(tag[id]){
return tag[id];
}
else{
int mid=(L+R)>>, s1=, s2=, res;
if(l<=mid)
s1=extract(id<<, L, mid, l, r);
if(r>mid)
s2=extract(id<<|, mid+, R, l, r);
res=s1?s1:s2;
if(l<=L&&R<=r)
tag[id]=res;
return res;
}
}
但这个写法是错的,和Tutorial的描述不相符,并没有把(l, r)的旧区间(departments)合并。
int query(int id, int L, int R, int pos){
if(tag[id]) return tag[id];
int mid=(L+R)>>;
if(pos<=mid)
return query(id<<, L, mid, pos);
return query(id<<|, mid+, R, pos);
}
//extract info. of target subsegment within node (L, R)
void extract(int id, int L, int R, int l, int r, int label){
if(tag[id]){
tag[id]=label;
}
else{
if(l<=L&&R<=r)
tag[id]=label;
else{
int mid=(L+R)>>;
if(l<=mid)
extract(id<<, L, mid, l, r, label);
if(r>mid)
extract(id<<|, mid+, R, l, r, label);
}
}
}
这样才是正确的姿势。
another version
int query(int id, int L, int R, int pos){
if(tag[id]) return tag[id];
int mid=(L+R)>>;
if(pos<=mid)
return query(id<<, L, mid, pos);
return query(id<<|, mid+, R, pos);
}
//extract info. of target subsegment within node (L, R)
void extract(int id, int L, int R, int l, int r, int label){
if(tag[id]){
tag[id]=label;
}
else{
if(l<=L&&R<=r)
tag[id]=label;
else{
int mid=(L+R)>>;
if(l<=mid)
extract(id<<, L, mid, l, r, label);
if(r>mid)
extract(id<<|, mid+, R, l, r, label);
if(tag[id<<]==tag[id<<|])
tag[id]=tag[id<<];
}
}
}
最后一句
if(tag[id<<1]==tag[id<<1|1])
tag[id]=tag[id<<1];
不清楚要不要加上
现在看 Tutorial 的第三段,为了支持操作1,再加上一个并查集(DSU)来维护不同区间之间的等价性(equivalence)
注意要将取出的旧区间合并,开始我是这么写的:
#include<bits/stdc++.h>
using namespace std;
const int MAX_N=2e5+;
//DSU
int par[MAX_N];
void init(int n){
for(int i=; i<=n; i++)
par[i]=i;
}
int find(int x){
int root=x;
while(par[root]!=root)
root=par[root];
int tmp;
while(par[x]!=x){
tmp=par[x];
par[x]=root;
x=tmp;
}
return root;
}
void unite(int x, int y){
x=find(x);
y=find(y);
par[x]=y;
}
//ST
int tag[MAX_N<<];
void build(int id, int l, int r){
if(l==r)
tag[id]=l;
else{
int mid=(l+r)>>;
build(id<<, l, mid);
build(id<<|, mid+, r);
}
}
int query(int id, int L, int R, int pos){
if(tag[id])
return tag[id];
int mid=(L+R)>>;
if(pos<=mid)
return query(id<<, L, mid, pos);
return query(id<<|, mid+, R, pos);
}
void extract(int id, int L, int R, int l, int r, int lable){
if(tag[id]){
unite(tag[id], lable);//extract old segments
tag[id]=lable;
}
else{
int mid=(L+R)>>;
if(l<=mid)
extract(id<<, L, mid, l, r, lable);
if(r>mid)
extract(id<<|, mid+, R, l, r, lable);
if(tag[id>>]==tag[id>>|])
tag[id]=tag[id>>];
}
} int main(){
//freopen("in", "r", stdin);
int n, q;
scanf("%d%d", &n, &q);
init(n);
build(, , n);
int type, x, y, sx, sy;
while(q--){
scanf("%d%d%d", &type, &x, &y);
switch(type){
case :
sx=query(, , n, x);
sy=query(, , n, y);
if(sx!=sy)
unite(sx, sy);
break;
case :
sx=query(, , n, x);
extract(, , n, x, y, sx);
break;
case :
sx=query(, , n, x);
sy=query(, , n, y);
//printf("%d %d\n", sx, sy);
puts(find(sx)==find(sy)?"YES":"NO");
break;
}
}
return ;
}
果断又 T 了,原因是我没有完全领会 Tutorial 的意思,完全按照里面讲的去写,其实完全没必要 query。
后来看到了 Codeforces 的一个 AC 代码,短得~
#include<cstdio>
const int N=2e5+;
int f[N],next[N];
int find(int x){return x==f[x]?x:(f[x]=find(f[x]));}
void Union(int a,int b){f[find(a)]=find(b);}
int n,q;
int main()
{
scanf("%d%d",&n,&q);
for(int i=;i<=n;i++)
{
f[i]=i;
next[i]=i+;
}
while(q--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
if(a==) Union(b,c);
else if(a==)
{
int fa = find(c);
for(int i=b;i<=c;)
{
f[find(i)]=fa;
int tmp=i;
i=next[i];
next[tmp]=next[c];
}
}
else if(a==) puts(find(b)==find(c)?"YES":"NO");
}
}
看过后明白了题解中“Also for queries of the second type it's important not to forget to call merge from all extracted segments.”的真正含义。在取出旧区间时,要将它们合并起来,这样查询的时候就不用先query员工所在的区间,再到DSU里面查询两个区间是否属于同一集合 (department) 了,直接判断两人的id是否在同一集合中就好了。
然后正确的写法是
#include<bits/stdc++.h>
using namespace std;
const int MAX_N=2e5+;
//DSU
int par[MAX_N];
void init(int n){
for(int i=; i<=n; i++)
par[i]=i;
}
int find(int x){
int root=x;
while(par[root]!=root)
root=par[root];
int tmp;
while(par[x]!=x){
tmp=par[x];
par[x]=root;
x=tmp;
}
return root;
}
void unite(int x, int y){
par[find(x)]=find(y);
}
//ST
int tag[MAX_N<<];
void build(int id, int l, int r){
if(l==r)
tag[id]=l;
else{
int mid=(l+r)>>;
build(id<<, l, mid);
build(id<<|, mid+, r);
}
}
//extract info. of target subsegment within node (L, R)
int extract(int id, int L, int R, int l, int r){
if(tag[id]){
return tag[id];
}
else{
int mid=(L+R)>>, s1=, s2=, res;
if(l<=mid)
s1=extract(id<<, L, mid, l, r);
if(r>mid)
s2=extract(id<<|, mid+, R, l, r);
if(s1&&s2){
unite(s1, s2);
res=s1;
}
else res=s1^s2;
if(l<=L&&R<=r)
tag[id]=res;
return res;
}
} int main(){
//freopen("in", "r", stdin);
int n, q;
scanf("%d%d", &n, &q);
init(n);
build(, , n);
int type, x, y;
while(q--){
scanf("%d%d%d", &type, &x, &y);
switch(type){
case :
unite(x, y);
break;
case :
extract(, , n, x, y);
break;
case :
puts(find(x)==find(y)?"YES":"NO");
break;
}
}
return ;
}
总结
看了题解后,感觉我开始的思路是对的,就是在如何处理操作2上没有想到好办法。参考题解给出的区间+DSU解法时,反而受到误导。最后发现这道题其实还是一道并查集的题,线段树只是用来辅助操作2的区间合并的(题解描述的貌似刚好相反)。
那种相当短的写法恐怕是一种别人都知道我还不知道的 practice,必须学习一下,但是其复杂度恐怕不是 $q\log(n)$,极有可能还要低些,线段树写法的复杂度是 $q\log(n)$ 无疑。
但线段树还是处理区间问题一种普适工具,应该 get 到其精髓,学会灵活运用。
EDIT 2018/3/29
今天再来看这篇随笔已经看不懂了,当初写的太乱了。
Codeforces 556D Restructuring Company的更多相关文章
- CodeForces - 566D Restructuring Company 并查集的区间合并
Restructuring Company Even the most successful company can go through a crisis period when you have ...
- CodeForces 566D Restructuring Company (并查集+链表)
题意:给定 3 种操作, 第一种 1 u v 把 u 和 v 合并 第二种 2 l r 把 l - r 这一段区间合并 第三种 3 u v 判断 u 和 v 是不是在同一集合中. 析:很容易知道是用并 ...
- codeforces 566D D. Restructuring Company(并查集)
题目链接: D. Restructuring Company time limit per test 2 seconds memory limit per test 256 megabytes inp ...
- VK Cup 2015 - Finals, online mirror D. Restructuring Company 并查集
D. Restructuring Company Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/contest/5 ...
- D. Restructuring Company 并查集 + 维护一个区间技巧
http://codeforces.com/contest/566/problem/D D. Restructuring Company time limit per test 2 seconds m ...
- Codeforces 566 D. Restructuring Company
Description 一开始有 \(n\) 个元素,可以进行几个操作. 合并 \(x,y\) . 合并 \(x,x+1,...,y\) . 询问 \(x,y\) 是否在一个集合中. Sol 并查集+ ...
- [刷题]Codeforces 794C - Naming Company
http://codeforces.com/contest/794/problem/C Description Oleg the client and Igor the analyst are goo ...
- CodeForces 125E MST Company
E. MST Company time limit per test 8 seconds memory limit per test 256 megabytes input standard inpu ...
- Codeforces 1062 E - Company
E - Company 思路: 首先,求出每个点的dfs序 然后求一些点的公共lca, 就是求lca(u, v), 其中u是dfs序最大的点, v是dfs序最小的大点 证明: 假设o是这些点的公共lc ...
随机推荐
- RecyclerView (一) 基础知识
RecyclerView是什么? RecyclerView是一种新的视图组,目标是为任何基于适配器的视图提供相似的渲染方式.它被作为ListView和GridView控件的继承者,在最新的suppor ...
- VS2010引用App_Code下的类文件问题解决方法
原文连接:http://blog.csdn.net/zjlovety/article/details/7658528 VS2020中“添加ASP.NET文件夹”里没有App_Code,添加普通文件夹然 ...
- 3D数学基础:四元数与欧拉角之间的转换
在3D图形学中,最常用的旋转表示方法便是四元数和欧拉角,比起矩阵来具有节省存储空间和方便插值的优点.本文主要归纳了两种表达方式的转换,计算公式采用3D笛卡尔坐标系: 单位四元数可视化为三维矢量加上第四 ...
- memcached工作原理与优化建议
申明,本文为转载文:http://my.oschina.net/liuxd/blog/63129 工作原理 基本概念:slab,page,chunk. slab,是一个逻辑概念.它是在启动memcac ...
- JAVABEAN连接各数据库
1. 连接ACCESS( AccessBean.java) package access; import java.sql.*; public class AccessBean { String d ...
- Java系列:关于Java中的桥接方法
这两天在看<Java核心技术 卷1>的泛型相关章节,其中说到了在泛型子类中override父类的泛型方法时,编译器会自动生成一个桥接方法,这块有点看不明白. 书上的例子代码如下: publ ...
- survival analysis 生存分析与R 语言示例 入门篇
原创博客,未经允许,不得转载. 生存分析,survival analysis,顾名思义是用来研究个体的存活概率与时间的关系.例如研究病人感染了病毒后,多长时间会死亡:工作的机器多长时间会发生崩溃等. ...
- 《Java程序设计》第五次实验实验报告
实验封面 一.实验内容 1.阅读理解源码进入07_httpd所在的目录,使用vi编辑器理解源代码. 2.编译应用程序使用gcc编译器,分别对文件夹下的copy.c和httpd.c进行编译,出现copy ...
- FPGA学习之基本结构
如何学习FPGA中提到第一步:学习.了解FPGA结构,FPGA到底是什么东西,芯片里面有什么,不要开始就拿个开发板照着别人的东西去编程.既然要开始学习FPGA,那么就应该从其基本结构开始.以下内容是我 ...
- HoloLens开发手记 - Unity之摄像头篇
当你穿戴好HoloLens后,你就会处在全息应用世界的中心.当你的项目开启了"Virtual Reality Support"选项并选中了"Windows Hologra ...