这题是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. 浅谈设计模式--组合模式(Composite Pattern)

    组合模式(Composite Pattern) 组合模式,有时候又叫部分-整体结构(part-whole hierarchy),使得用户对单个对象和对一组对象的使用具有一致性.简单来说,就是可以像使用 ...

  2. unity3d 三分钟实现简单的赛车漂移

    提到赛车游戏,大家最关心的应该就是漂移吧?! 从学unity开始,我就一直在断断续续的研究赛车 因为自己技术太烂.悟性太差等原因,我走了不少弯路 也许你会说,网上那么多资料,你不会查啊 是啊!网上一搜 ...

  3. Allegro 中手动制作螺丝孔封装

    以直径2.5mm的螺丝孔为例: 添加过孔,通常过孔的尺寸稍大于实际的螺丝直径,这里设置为2.8mm的直径. 添加过孔焊盘的其他属性. 制作边上的小焊盘. 新建Package Symbol然后点击Lay ...

  4. hdu5481 Desiderium

    链接 Desiderium 题意 给定n条线段,从中选取若干条,共有2n种选法(因为每一条线段有两种方法:选或者不选). 每一种选法都对应一个长度,也就是所选线段的并集长度. 求这2n种选法长度之和. ...

  5. Hibernate 的dialect 大全

    RDBMS 方言 DB2 org.hibernate.dialect.DB2Dialect DB2 AS/400 org.hibernate.dialect.DB2400Dialect DB2 OS3 ...

  6. Putty SSH简单使用

    本地的puttygen生出的秘钥,公钥传到服务器上连接会报错 Server refused our key. 一般我们建议都在服务器上生成秘钥,把私钥下载下来.加载到putty认证中 01.在服务器上 ...

  7. Collections的应用

    Collection : 接口  Collections : 集合的工具类    Arrays (数组的工具类)  只能操作list集合    说出Collection和Collections 的区别 ...

  8. javaweb写的在线聊天应用

    写这个玩意儿就是想练练手, 用户需要登陆才能在线聊天,不要依赖数据库, 不需要数据库的操作, 所有的数据都是保存在内存中, 如果服务器一旦重启,数据就没有了: 登录界面: 聊天界面: 左侧是在线的用户 ...

  9. Mybatis 自动生成代码

    准备条件: 将下面的文件放入同一目录下 操作步骤: 1/ 在 generatorConfig.xml 中配置相关的参数,与需要被自动生成的表 也可以 执行项目中的MybatisConfigAutoGe ...

  10. bzoj 3743

    这道题用到了4个dfs,分别是找出所有家的最小生成树,找出一点距离树的最小距离,找出每个点儿子距离的最大值(不包括父亲,也就是指不包括根节点的子树),用父亲的值来更新自己 因为我们可以知道:如果我们在 ...