HDU 5923 Prediction
这题是2016 CCPC 东北四省赛的B题, 其实很简单. 现场想到的就是正解, 只是在合并两个并查集这个问题上没想清楚.
做法
并查集合并 + 归并
- 对每个节点 $u$, 将 $u$ 到根的那些边添到一个初始为空的并查集中, 得到的并查集记作 $a_u$.
- 询问相当于将 $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了.
- 根本不用归并, 直接逐个合并就好了.
- 根本不用
b[i].copy(a[x]);, 只要从一个边集为空的图 (以下简称"空图") 开始, 不断把$k$个并查集合并进去就好了. - 不从空图开始, 而从某个并查集开始, 会快很多.
#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的更多相关文章
- HDU 5923 Prediction(2016 CCPC东北地区大学生程序设计竞赛 Problem B,并查集)
题目链接 2016 CCPC东北地区大学生程序设计竞赛 B题 题意 给定一个无向图和一棵树,树上的每个结点对应无向图中的一条边,现在给出$q$个询问, 每次选定树中的一个点集,然后真正被选上的是这 ...
- HDU 1338 Game Prediction
http://acm.hdu.edu.cn/showproblem.php?pid=1338 Problem Description Suppose there are M people, inclu ...
- HDU 1338 Game Prediction【贪心】
解题思路: 给出 n m 牌的号码是从1到n*m 你手里的牌的号码是1到n*m之间的任意n个数,每张牌都只有一张,问你至少赢多少次 可以转化为你最多输max次,那么至少赢n-max次 而最多输max ...
- HDU——PKU题目分类
HDU 模拟题, 枚举1002 1004 1013 1015 1017 1020 1022 1029 1031 1033 1034 1035 1036 1037 1039 1042 1047 1048 ...
- HDU 5925 Coconuts 【离散化+BFS】 (2016CCPC东北地区大学生程序设计竞赛)
Coconuts Time Limit: 9000/4500 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total Su ...
- hdu 5895 广义Fibonacci数列
Mathematician QSC Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Othe ...
- HDOJ 2111. Saving HDU 贪心 结构体排序
Saving HDU Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total ...
- 【HDU 3037】Saving Beans Lucas定理模板
http://acm.hdu.edu.cn/showproblem.php?pid=3037 Lucas定理模板. 现在才写,noip滚粗前兆QAQ #include<cstdio> #i ...
- hdu 4859 海岸线 Bestcoder Round 1
http://acm.hdu.edu.cn/showproblem.php?pid=4859 题目大意: 在一个矩形周围都是海,这个矩形中有陆地,深海和浅海.浅海是可以填成陆地的. 求最多有多少条方格 ...
随机推荐
- 一道int与二进制加减题
int dis_data = 32769; if( dis_data > 0x7fff) dis_data -= 0xffff; printf("%d\n",dis_dat ...
- 汤姆大叔的6道javascript编程题题解
看汤姆大叔的博文,其中有篇(猛戳这里)的最后有6道编程题,于是我也试试,大家都可以先试试. 1.找出数字数组中最大的元素(使用Math.max函数) var a = [1, 2, 3, 6, 5, 4 ...
- express+session实现简易身份认证
本文摘录自<Nodejs学习笔记>,更多章节及更新,请访问 github主页地址.欢迎加群交流,群号 197339705. 文章概览 本文基于express.express-session ...
- 20151023 - discuz 6 中 insenz 营销推广失效的问题
将很久之前的论坛重新放在网络上,发现首页打开非常慢,用 Web Inspector 检查,发现 insenz.com 已失效导致. 解决办法: 1.进入数据库:执行 SELECT * FROM cdb ...
- Windows Azure 云服务角色架构
当我们使用VS发布一个Cloud Service或者在Portal上上传发布包后,就能启动和运行一个云服务,可以保护WebRole,WorkerRole的一个或者多个实例. Windows Azure ...
- C#中的System.Speech命名空间初探
本程序是口算两位数乘法,随机生成两个两位数,用语音读出来.然后开启语音识别,接受用户输入,知道答案正确关闭语音识别.用户说答案时,可以说“再说一遍”重复题目. 关键是GrammarBuilder和Ch ...
- HFS汉化版|简易HTTP服务器
专为个人用户所设计的 HTTP 档案系统 - Http File Server,如果您觉得架设 FTP Server 太麻烦,那么这个软件可以提供您更方便的档案传输系统,下载后无须安装,只要解压缩后执 ...
- 【Magenta 项目初探】手把手教你用Tensorflow神经网络创造音乐
原文链接:http://www.cnblogs.com/learn-to-rock/p/5677458.html 偶然在网上看到了一个让我很感兴趣的项目 Magenta,用Tensorflow让神经网 ...
- dmesg 显示内核消息
显示内核消息 dmesg [options] dmesg 可以用来显示存储在内核环缓冲区中的消息 系统启动时,内核会用硬件和模块初始化的相关消息填充其环缓冲区.内核环缓冲区中的消息常常用于诊断系统问题 ...
- python基础-内置函数详解
一.内置函数(python3.x) 内置参数详解官方文档: https://docs.python.org/3/library/functions.html?highlight=built#ascii ...