这题是2016 CCPC 东北四省赛的B题, 其实很简单. 现场想到的就是正解, 只是在合并两个并查集这个问题上没想清楚.

做法

并查集合并 + 归并

  1. 对每个节点 $u$, 将 $u$ 到根的那些边添到一个初始为空的并查集中, 得到的并查集记作 $a_u$.
  2. 询问相当于将 $k$ 个并查集合并. 采用二路归并, 合并次数是 $O(n \cdot \log(n))$.

    $ n/2 + n/4 + n/8 + \dots + 1 = O(n \cdot \log(n)) $

合并两个并查集

详细讨论将并查集 $B$ 合并到并查集 $A$ 中这一问题.

这个问题与

给定两无向图 $A, B, V_B \subset V_A; \quad A(E_A, V_A) \to A'( E_A, E_A \cup E_B) $.

等价.

做法

$ \forall u \in E_B, \quad A.\mathrm{unite}(u, B.\mathrm{root}(u)) $

正确性

只要验证

在$B$中连通的任意两点 $u, v$, 在$ A'$中也连通.

是否满足.

Implementation

#include <bits/stdc++.h>
using namespace std; const int N{1<<9};
const int M=1e4+5; int n, m; struct DSU{
int par[N];
int cnt; int find(int x){
return par[x]==x?x: par[x]=find(par[x]);
} void unite(int x, int y){
x=find(x);
y=find(y);
if(x!=y){
par[x]=y;
--cnt;
}
} void unite(DSU &a){
for(int i=1; i<=n; i++){
unite(find(i), a.find(i)); // ?
}
} void init(){
for(int i=1; i<=n; i++){
par[i]=i;
}
cnt=n;
} void copy(const DSU &a){
for(int i=1; i<=n; i++){
par[i]=a.par[i];
}
cnt=a.cnt;
}
}; DSU a[M], b[M]; vector<int> g[M]; struct Edge{
int u, v;
void read(){
scanf("%d%d", &u, &v);
}
}E[M]; void dfs(int u, int f){
a[u].copy(a[f]);
a[u].unite(E[u].u, E[u].v); for(auto v: g[u]){
dfs(v, u);
}
} void solve(int n){
for(int i=1; i<n; i<<=1){ // error-prone
for(int j=0; j+i<n; j+=i<<1){
b[j].unite(b[j+i]);
}
}
printf("%d\n", b[0].cnt);
} // int par[M]; int main(){ int T, cas{};
for(cin>>T; T--; ){
printf("Case #%d:\n", ++cas);
// int n, m;
cin>>n>>m; for(int i=1; i<=m; ++i){
g[i].clear();
} for(int i=2; i<=m; i++){
// scanf("%d", par+i);
int fa;
scanf("%d", &fa);
g[fa].push_back(i);
} for(int i=1; i<=m; ++i){
E[i].read();
} a[0].init();
dfs(1, 0); int q;
cin>>q;
for(; q--; ){
int k;
scanf("%d", &k);
for(int i=0; i<k; i++){
int x;
scanf("%d", &x);
b[i].copy(a[x]);
}
solve(k);
}
}
return 0;
}

Pitfalls

归并

for(int i=1; i<n; i<<=1){   // error-prone
for(int j=0; j+i<n; j+=i<<1){
b[j].unite(b[j+i]);
}
}

容易写错.

我第一发是这样写的

for(int i=2; i<=n; i<<=1){
for(int j=0; j+i/2<n; j+=i){
b[j].unite(b[j+i/2]);
}
}

n==3时, 只做了1轮归并.

应采纳第一种写法, 很清楚.


UPD

太SB了.

  1. 根本不用归并, 直接逐个合并就好了.
  2. 根本不用 b[i].copy(a[x]); , 只要从一个边集为空的图 (以下简称"空图") 开始, 不断把$k$个并查集合并进去就好了.
  3. 不从空图开始, 而从某个并查集开始, 会快很多.
#include <bits/stdc++.h>
using namespace std; const int N{1<<9};
const int M=1e4+5; int n, m; struct DSU{
int par[N];
int cnt; int find(int x){
return par[x]==x?x: par[x]=find(par[x]);
} void unite(int x, int y){
x=find(x);
y=find(y);
if(x!=y){
par[x]=y;
--cnt;
}
} void unite(DSU &a){
for(int i=1; i<=n; i++){
unite(find(i), a.find(i)); // ?
}
} void init(){
for(int i=1; i<=n; i++){
par[i]=i;
}
cnt=n;
} void copy(const DSU &a){
for(int i=1; i<=n; i++){
par[i]=a.par[i];
}
cnt=a.cnt;
}
}; DSU a[M], b[M]; vector<int> g[M]; struct Edge{
int u, v;
void read(){
scanf("%d%d", &u, &v);
}
}E[M]; void dfs(int u, int f){
a[u].copy(a[f]);
a[u].unite(E[u].u, E[u].v); for(auto v: g[u]){
dfs(v, u);
}
} int solve(int n){
if(k==0){
return n;
}
int x;
scanf("%d", &x);
a[0].copy(a[x]);
for(int i=1; i<n; i++){
scanf("%d", &x);
a[0].unite(a[x]);
}
return a[0].cnt;
} int main(){ int T, cas{};
for(cin>>T; T--; ){
printf("Case #%d:\n", ++cas); cin>>n>>m; for(int i=1; i<=m; ++i){
g[i].clear();
} for(int i=2; i<=m; i++){
// scanf("%d", par+i);
int fa;
scanf("%d", &fa);
g[fa].push_back(i);
} for(int i=1; i<=m; ++i){
E[i].read();
} a[0].init();
dfs(1, 0); int q;
cin>>q;
for(; q--; ){
int k;
scanf("%d", &k);
printf("%d\n", solve(k));
}
}
return 0;
}

HDU 5923 Prediction的更多相关文章

  1. HDU 5923 Prediction(2016 CCPC东北地区大学生程序设计竞赛 Problem B,并查集)

    题目链接  2016 CCPC东北地区大学生程序设计竞赛 B题 题意  给定一个无向图和一棵树,树上的每个结点对应无向图中的一条边,现在给出$q$个询问, 每次选定树中的一个点集,然后真正被选上的是这 ...

  2. HDU 1338 Game Prediction

    http://acm.hdu.edu.cn/showproblem.php?pid=1338 Problem Description Suppose there are M people, inclu ...

  3. HDU 1338 Game Prediction【贪心】

    解题思路: 给出 n  m 牌的号码是从1到n*m 你手里的牌的号码是1到n*m之间的任意n个数,每张牌都只有一张,问你至少赢多少次 可以转化为你最多输max次,那么至少赢n-max次 而最多输max ...

  4. HDU——PKU题目分类

    HDU 模拟题, 枚举1002 1004 1013 1015 1017 1020 1022 1029 1031 1033 1034 1035 1036 1037 1039 1042 1047 1048 ...

  5. HDU 5925 Coconuts 【离散化+BFS】 (2016CCPC东北地区大学生程序设计竞赛)

    Coconuts Time Limit: 9000/4500 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Su ...

  6. hdu 5895 广义Fibonacci数列

    Mathematician QSC Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Othe ...

  7. HDOJ 2111. Saving HDU 贪心 结构体排序

    Saving HDU Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total ...

  8. 【HDU 3037】Saving Beans Lucas定理模板

    http://acm.hdu.edu.cn/showproblem.php?pid=3037 Lucas定理模板. 现在才写,noip滚粗前兆QAQ #include<cstdio> #i ...

  9. hdu 4859 海岸线 Bestcoder Round 1

    http://acm.hdu.edu.cn/showproblem.php?pid=4859 题目大意: 在一个矩形周围都是海,这个矩形中有陆地,深海和浅海.浅海是可以填成陆地的. 求最多有多少条方格 ...

随机推荐

  1. web 前端常用组件【04】Datetimepicker 和 Lodop

    web项目中日期选择器和打印这两个功能是非常常见,将使用过的日期和打印控件,在这里总结归纳,为方便后面使用. 1.Datetimepicker a.官方API:http://www.bootcss.c ...

  2. DOM与元素节点内联样式

    获取.设置及移除单个内联 CSS 属性 每个 HTML 元素都有个 style 属性,可以用来插入针对该元素的内联 CSS 属性. <div style='background-color:bl ...

  3. [转]linux 系统监控、诊断工具之 IO wait

    1.问题: 最近在做日志的实时同步,上线之前是做过单份线上日志压力测试的,消息队列和客户端.本机都没问题,但是没想到上了第二份日志之后,问题来了: 集群中的某台机器 top 看到负载巨高,集群中的机器 ...

  4. Sql server使用Merge关键字做插入或更新操作

    Merge是关于对于两个表之间的数据进行操作的. 要使用Merge的场景比如: 数据同步 数据转换 基于源表对目标表做Insert,Update,Delete操作 MERGE语句的基本语法: MERG ...

  5. LLC 逻辑链路控制

    LLC  协  议 4.2.1 LLC帧格式 LLC协议定义了LLC层之间通信的帧格式,参见图4.3. 图4.3  LLC帧格式 LLC帧格式中各个字段的含义如下: ① 服务访问点(SAP)地址:SA ...

  6. JavaScript学习笔记- 正则表达式常用验证

    <div> <h1>一.判断中国邮政编码匹配</h1> <p>分析:中国邮政编码都是6位,且为纯数字</p> <div>邮政编码 ...

  7. BroadcoastReceiver之短信到来监听和获取内容

    废话就不说了,新建类继承,然后配置Manifest.xml:如下 <!--需要给一个接收短信的权限 --> <uses-permission android:name="a ...

  8. 链接错误-库冲突(libcmt.lib和libcmtd.lib)

    在同一个项目中,所有的源文件必须链接相同的C运行时库.如果某一文件用了Multithreaded DLL版本,而其他文件用了Single-Threaded或者Multithreaded版本的库,也就是 ...

  9. 数据源DBCP一二

    其实DBCP这个数据源实际上和com.alibaba.druid.pool.DruidDataSource 是差不多的

  10. js实现倒计时效果

    <!DOCTYPE html><head><meta http-equiv="Content-Type" content="text/htm ...