Codeforces 571D - Campus(并查集+线段树+DFS 序,hot tea)
看到集合的合并,可以本能地想到并查集。
不过这题的操作与传统意义上的并查集不太一样,传统意义上的并查集一般是用来判断连通性的,而此题还需支持赋值、集合内整体加等操作,似乎就有些难处理。
我们不妨考虑此题的弱化版:没有第二类集合的版本。也就是要求支持合并两个集合,集合整体加某个数 \(x\),单点查询三个操作。
很明显集合整体加某个数 \(x\) 就相当于在并查集根节点处打一个 \(+x\) 的标记,查询就暴力跳父亲求出待询问点到根节点路径上所有点的标记之和。不过稍微想想就知道这个做法是假的,因为如果直接采用按秩合并会导致标记加的点集出现问题,举个例子,比方说我们要合并 \(x,y\) 两个集合,其中 \(x\) 为父亲,我们假设原本 \(x\) 点上有一个 \(+v\) 的标记,显然这个标记所管辖的点的集合是原本 \(x\) 所在的连通块,但经过我们这么一合并,我们默认每个标记管辖的点的集合是其子树,这也就意味着原本 \(y\) 所在的集合也被错误地打上了 \(+v\) 的标记,这样显然会出问题。我们考虑换个合并的思路,每次合并建立一个虚点 \(z\),并让 \(x,y\) 的父亲都为 \(z\),这样就能保证每次标记管辖的点集是正确的了。但这样还会导致一个问题,那就是树的深度可能很大,暴力条父亲会 TLE。故考虑把操作离线下来,先建出最后的森林出来并求出 DFS 序,每次修改用线段树在待修改点的子树内整体加 \(v\),查询就执行一次线段树上的单点查询即可,很明显森林里的点数最多为 \(n+q\),故总复杂度 \((n+q)\log(n+q)\)。
接下来考虑有第二类集合的版本,这个子树清空貌似维护起来有些困难,不过我们考虑换个思路,使用差分的思想,询问某个点 \(x\) 的值可以转化为此时 \(x\) 点的权值减去上一次 \(x\) 点被清空前 \(x\) 点的权值。故我们只用求出每次询问,带询问点上一次被清空的时间即可。那么这个东西怎么求呢?其实与弱化版维护的思路大同小异,先离线建出第二类集合的森林出来,每次清空一个节点 \(x\) 就相当于在 \(x\) 子树内所有点赋值 \(t\)(其中 \(t\) 为此次操作的时间),表示这些点上一次被清空是时间 \(t\),这个可以用区间赋值、单点查询的线段树维护。
到这里题目基本上已经做完了,不妨理理思路。我们先将询问离线下来,建出两类集合并查集所形成的森林,并对两个森林各进行一遍 DFS 求出每个点的 DFS 序,第二次扫描全部与第二类集合有关的操作,对于区间清空的操作就在子树对应 DFS 序的区间内执行区间赋值操作,如果遇到询问就进行一遍线段树上的单点查询,记录下待询问点上一次被清空的时间。第三次扫描全部与第一类集合有关的操作,对于区间加的操作就在子树对应 DFS 序的区间内执行区间加操作,如果遇到询问就进行一遍线段树上的单点查询,这样就可以求出答案了。
时间复杂度 \((n+q)\log(n+q)\)。
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
namespace fastio{
#define FILE_SIZE 1<<23
char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
inline void putc(char x){(*p3++=x);}
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=0;
while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(neg) x=(~x)+1;
}
template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=5e5;
int n,qu;
struct solver{
int bel[MAXN+5],siz[MAXN*2+5],tot;
void init(){
for(int i=1;i<=n;i++) bel[i]=i,siz[i]=1;
tot=n;
}
int hd[MAXN*2+5],nxt[MAXN*2+5],to[MAXN*2+5],ec=0;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
void merge(int x,int y){
tot++;adde(tot,bel[x]);adde(tot,bel[y]);
siz[x]+=siz[y];siz[y]=0;bel[y]=0;bel[x]=tot;
}
int dfn[MAXN*2+5],sz[MAXN*2+5],tim=0;
void dfs(int x){
dfn[x]=++tim;sz[x]=1;
for(int e=hd[x];e;e=nxt[e]) dfs(to[e]),sz[x]+=sz[to[e]];
}
} s1,s2;
struct event{int opt,x,y;} q[MAXN+5];
vector<int> clr[MAXN+5];
ll res[MAXN+5];
struct seg1{
struct node{int l,r;ll v,lz;} s[MAXN*8+5];
void build(int k,int l,int r){
s[k].l=l;s[k].r=r;if(l==r) return;
int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
}
void pushdown(int k){
if(s[k].lz){
s[k<<1].v+=s[k].lz*(s[k<<1].r-s[k<<1].l+1);
s[k<<1].lz+=s[k].lz;
s[k<<1|1].v+=s[k].lz*(s[k<<1|1].r-s[k<<1|1].l+1);
s[k<<1|1].lz+=s[k].lz;
s[k].lz=0;
}
}
void modify(int k,int l,int r,int x){
if(l<=s[k].l&&s[k].r<=r){
s[k].v+=1ll*x*(s[k].r-s[k].l+1);s[k].lz+=x;
return;
} pushdown(k);int mid=s[k].l+s[k].r>>1;
if(r<=mid) modify(k<<1,l,r,x);
else if(l>mid) modify(k<<1|1,l,r,x);
else modify(k<<1,l,mid,x),modify(k<<1|1,mid+1,r,x);
s[k].v=s[k<<1].v+s[k<<1|1].v;
}
ll query(int k,int x){
if(s[k].l==s[k].r) return s[k].v;
pushdown(k);int mid=s[k].l+s[k].r>>1;
if(x<=mid) return query(k<<1,x);
else return query(k<<1|1,x);
}
} st1;
struct seg2{
struct node{int l,r,tag;} s[MAXN*8+5];
void build(int k,int l,int r){
s[k].l=l;s[k].r=r;if(l==r) return;
int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
}
void pushdown(int k){
if(s[k].tag){
s[k<<1].tag=s[k].tag;s[k<<1|1].tag=s[k].tag;
s[k].tag=0;
}
}
void modify(int k,int l,int r,int x){
if(l<=s[k].l&&s[k].r<=r){s[k].tag=x;return;}
pushdown(k);int mid=s[k].l+s[k].r>>1;
if(r<=mid) modify(k<<1,l,r,x);
else if(l>mid) modify(k<<1|1,l,r,x);
else modify(k<<1,l,mid,x),modify(k<<1|1,mid+1,r,x);
}
int query(int k,int x){
if(s[k].l==s[k].r) return s[k].tag;
pushdown(k);int mid=s[k].l+s[k].r>>1;
if(x<=mid) return query(k<<1,x);
else return query(k<<1|1,x);
}
} st2;
int main(){
scanf("%d%d",&n,&qu);s1.init();s2.init();
for(int i=1;i<=qu;i++){
char opt[5];scanf("%s",opt+1);
if(opt[1]=='U'){
int x,y;scanf("%d%d",&x,&y);s1.merge(x,y);
q[i].opt=1;q[i].x=x;q[i].y=y;
} else if(opt[1]=='M'){
int x,y;scanf("%d%d",&x,&y);s2.merge(x,y);
q[i].opt=2;q[i].x=x;q[i].y=y;
} else if(opt[1]=='A'){
int x;scanf("%d",&x);
q[i].opt=3;q[i].x=s1.bel[x];q[i].y=s1.siz[x];
} else if(opt[1]=='Z'){
int x;scanf("%d",&x);
q[i].opt=4;q[i].x=s2.bel[x];
} else{
int x;scanf("%d",&x);
q[i].opt=5;q[i].x=x;
}
}
s1.tot++;for(int i=1;i<=n;i++) if(s1.siz[i]) s1.adde(s1.tot,s1.bel[i]);
s2.tot++;for(int i=1;i<=n;i++) if(s2.siz[i]) s2.adde(s2.tot,s2.bel[i]);
s1.dfs(s1.tot);s2.dfs(s2.tot);
st1.build(1,1,s1.tot);st2.build(1,1,s2.tot);
for(int i=1;i<=qu;i++){
if(q[i].opt==4) st2.modify(1,s2.dfn[q[i].x],s2.dfn[q[i].x]+s2.sz[q[i].x]-1,i);
if(q[i].opt==5){
int tmp=st2.query(1,s2.dfn[q[i].x]);
if(tmp) clr[tmp].pb(i);
}
}
for(int i=1;i<=qu;i++){
if(q[i].opt==3) st1.modify(1,s1.dfn[q[i].x],s1.dfn[q[i].x]+s1.sz[q[i].x]-1,q[i].y);
if(q[i].opt==4){
for(int j=0;j<clr[i].size();j++){
int id=clr[i][j];
res[id]-=st1.query(1,s1.dfn[q[id].x]);
}
}
if(q[i].opt==5){
res[i]+=st1.query(1,s1.dfn[q[i].x]);
printf("%lld\n",res[i]);
}
}
return 0;
}
Codeforces 571D - Campus(并查集+线段树+DFS 序,hot tea)的更多相关文章
- cf1278D——树的性质+并查集+线段树/DFS判环
昨天晚上本来想认真打一场的,,结果陪女朋友去了.. 回来之后看了看D,感觉有点思路,结果一直到现在才做出来 首先对所有线段按左端点排序,然后用并查集判所有边是否联通,即遍历每条边i,和前一条不覆盖它的 ...
- BZOJ3545 [ONTAK2010]Peaks kruskal 并查集 主席树 dfs序
欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目传送门 - BZOJ3545 题意概括 Description 在Bytemountains有N座山峰,每座山峰有他的高度 ...
- BZOJ3551 [ONTAK2010]Peaks加强版 kruskal 并查集 主席树 dfs序
欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目传送门 - BZOJ3551 题意概括 Description 在Bytemountains有N座山峰,每座山峰有他的高度 ...
- UVA1455 - Kingdom(并查集 + 线段树)
UVA1455 - Kingdom(并查集 + 线段树) 题目链接 题目大意:一个平面内,给你n个整数点,两种类型的操作:road x y 把city x 和city y连接起来,line fnum ...
- 并查集&线段树&树状数组&排序二叉树
超级无敌巨牛逼并查集(带权并查集)https://vjudge.net/problem/UVALive-4487 带删点的加权并查集 https://vjudge.net/problem/UVA-11 ...
- 【Codeforces576E_CF576E】Painting Edges(可撤销并查集+线段树分治)
题目 CF576E 分析: 从前天早上肝到明天早上qwq其实颓了一上午MC ,自己瞎yy然后1A,写篇博客庆祝一下. 首先做这题之前推荐一道很相似的题:[BZOJ4025]二分图(可撤销并查集+线段树 ...
- BZOJ 3910 并查集+线段树合并
思路: 1. 并查集+线段树合并 记得f[LCA]==LCA的时候 f[LCA]=fa[LCA] 2.LCT(并不会写啊...) //By SiriusRen #include <cstdio& ...
- Tsinsen A1505. 树(张闻涛) 倍增LCA,可持久化线段树,DFS序
题目:http://www.tsinsen.com/A1505 A1505. 树(张闻涛) 时间限制:1.0s 内存限制:512.0MB 总提交次数:196 AC次数:65 平均分: ...
- BZOJ_3252_攻略_线段树+dfs序
BZOJ_3252_攻略_线段树+dfs序 Description 题目简述:树版[k取方格数] 众所周知,桂木桂马是攻略之神,开启攻略之神模式后,他可以同时攻略k部游戏.今天他得到了一款新游戏< ...
随机推荐
- 初识HTML02
HTML 超文本标记语言 什么是超文本标记语言 浏览器能够解释和解析的语言 通过元素的形式构建页面结构和填充内容 构建HTML页面 构建页面的步骤 创建一个扩展名为.html和.html的页面文件 向 ...
- 我们一起来回顾一下Synchronized关键字吧
多线程一直Java开发中的难点,也是面试中的常客,趁着还有时间,打算巩固一下JUC方面知识,我想机会随处可见,但始终都是留给有准备的人的,希望我们都能加油!!! 沉下去,再浮上来,我想我们会变的不一样 ...
- [软工顶级理解组] Beta阶段事后分析
目录 设想和目标 计划 资源 变更管理 设计/实现 测试/发布 团队的角色,管理,合作 总结 质量提高 会议截图 设想和目标 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰 ...
- the Agiles Scrum Meeting 5
会议时间:2020.4.13 20:00 1.每个人的工作 今天已完成的工作 增量组:完成了增量开发的基础工作,初步完成了自动评测机制 issues:增量组:准备评测机制,增加仓库自动创建和管理 完善 ...
- .net,C#,Vb,F#,Asp,Asp.net区别以及作用和方向
.net是平台,其他都是运行在其.NET FrameWork环境下的 C#,Vb都是语言运行在.net 平台下 Asp,Asp.net 都是用来写Web网页的,但是Asp和Asp.net有区别 Asp ...
- eureka服务端的高可用
eureka client的高可用这个很简单,只需要向eureka服务端上多注册几个实例即可,那么eureka server端如何实现高可用呢?其实eureka server 端也是可以做为一个客户端 ...
- Noip模拟21(持续翻车)2021.7.20
读题总是读错是不是没救了... T1 Median 中位数:按顺序排列的一组数据中居于中间位置的数. 能用上的高亮符号都用上了... 当时忘了就离谱.... 理解什么是中位数(真是个憨憨)后就可以开始 ...
- Spark面试题(二)
首发于我的个人博客:Spark面试题(二) 1.Spark有哪两种算子? Transformation(转化)算子和Action(执行)算子. 2.Spark有哪些聚合类的算子,我们应该尽量避免什么类 ...
- [LGP2758]编辑距离
目录 题目 题目描述 输入格式 输出格式 输入输出样例 题目分析 状态转移方程 初始状态 结束状态 Code 题目 题目描述 设A和B是两个字符串.我们要用最少的字符操作次数,将字符串A转换为字符串B ...
- 对dy和Δy的浅薄理解
一.导数定义 当函数y=f(x)的自变量x在一点x0上产生一个增量Δx时,函数输出值的增量Δy与自变量增量Δx的比值在Δx趋于0时的极限a如果存在,a即为在x0处的导数,记作f'(x0)或df(x0) ...