这题是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. TinyFrame升级之十:WCF Rest Service注入IOC的心

    由于在实际开发中,Silverlight需要调用WebService完成数据的获取,由于之前我们一直采用古老的ASMX方式,生成的代理类不仅难以维护,而且自身没有提供APM模式的调用方式,导致在Sin ...

  2. GET请求参数为中文时乱码分析

    问题描述 近期做任务时,跟后端联调时遇到一个问题,前端发送get请求,当参数值有中文时,请求失败,请求参数变为乱码.(ps:一般当参数有中文时,很少使用get请求,而是使用post请求来传输数据,请求 ...

  3. Canvas之打字机游戏

    最近针对粒子化作了一点点的探究,决定结合其做个小游戏,于是这个简单的打字游戏出世了. 试玩地址:Typewriter game  仅在chrome下测试,请谨慎使用其他浏览器(特别是ff):加载速度有 ...

  4. dev gridcontrol纵向合并单元格设置

    1.要设置gridcontrol中指定列(columns中选中指定列)的AllowMerge属性为true; 2.要设置gridview中AllowCellMerge的属性为true; 3.如果只合并 ...

  5. 刷新SqlServer所有视图【存储过程】

    摘自:http://www.cnblogs.com/yashen/archive/2004/12/23/81000.html CREATE PROCEDURE RefreshAllView AS DE ...

  6. jQuery.uploadify-----文件上传带进度条,支持多文件上传的插件

    借鉴别人总结的uploadify:基于jquery的文件上传插件,支持ajax无刷新上传,多个文件同时上传,上传进行进度显示,控制文件上传大小,删除已上传文件. uploadify有两个版本,一个用f ...

  7. js的数组

    转载:http://blog.163.com/sammer_rui/blog/static/846200442010717900634/ https://developer.mozilla.org/z ...

  8. nodejs实现的简单接口

    var http = require('http'); var mysql = require('mysql'); var connection = mysql.createConnection({ ...

  9. Java--剑指offer(5)

    21.输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序.假设压入栈的所有数字均不相等.例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对 ...

  10. 【转】Web前端浏览器兼容初探

    原文地址:http://blog.jobbole.com/38638/ 前言 浏览器兼容是前端开发人员必须掌握的一个技能,但是初入前端的同学或者其他后台web开发同学往往容易选择忽略,而形成两个极端: ...