bzoj2049 线段树 + 可撤销并查集

https://www.lydsy.com/JudgeOnline/problem.php?id=2049

线段树真神奇

题意:给出一波操作,拆边加边以及询问两点是否联通。

听说常规方法是在线LCT,留坑。

如果说这个删边的操作是删除上一条边,那这自然是可撤销并查集的模板题,直接在线维护就可以了。

但是问题在于删除边的顺序是不可能固定的,要知道并查集是不可以随意撤销的。

万万没想到还有更加高妙的手法。

首先可以证明一条边的存在一定是一段或者多段连续的区间。

建立一条时间节点长度的线段树,结点维护一个边集合,每个位置表示的是当前这个时间下存在了哪几条边。

将上述的边区间全部加入,和常规的线段树不一样,这个不需要lazy标记也不需要Pushdown到下属区间,为了节省时间和空间,对于1 - N区间的边来说,我们仅仅把1号结点加上这条边。

然后用dfs的方法,进入结点时加上这些边,离开的时候删除这些边,在线段树的叶子节点上,并查集维护的就是当前时间的状态,离线的query直接询问即可。

时间复杂度,加边的整个过程mlogm,询问的过程节点数mlogm * 并查集find操作logm = mlogm

#include <map>
#include <set>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
#define For(i, x, y) for(int i=x;i<=y;i++)
#define _For(i, x, y) for(int i=x;i>=y;i--)
#define Mem(f, x) memset(f,x,sizeof(f))
#define Sca(x) scanf("%d", &x)
#define Sca2(x,y) scanf("%d%d",&x,&y)
#define Sca3(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define Scl(x) scanf("%lld",&x);
#define Pri(x) printf("%d\n", x)
#define Prl(x) printf("%lld\n",x);
#define CLR(u) for(int i=0;i<=N;i++)u[i].clear();
#define LL long long
#define ULL unsigned long long
#define mp make_pair
#define PII pair<int,int>
#define PIL pair<int,long long>
#define PLL pair<long long,long long>
#define pb push_back
#define fi first
#define se second
typedef vector<int> VI;
int read(){int x = 0,f = 1;char c = getchar();while (c<‘0‘ || c>‘9‘){if (c == ‘-‘) f = -1;c = getchar();}
while (c >= ‘0‘&&c <= ‘9‘){x = x * 10 + c - ‘0‘;c = getchar();}return x*f;}
const double eps = 1e-9;
const int maxn = 1e5 + 10;
const int maxm = 2e6 + 10;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
int N,M,K;
struct Query{
    int t,u,v;
    Query(){}
    Query(int t,int u,int v):t(t),u(u),v(v){}
}q[maxm];
struct Line{
    int op,u,v;
    Line(){}
    Line(int op,int u,int v):op(op),u(u),v(v){}
}line[maxm];
map<PII,int>Q;
int Stack[maxm],top;
int size[maxn],fa[maxn];
void init(){
    for(int i = 0; i <= N ; i ++){
        fa[i] = -1; size[i] = 0;
    }
    top = 0;
}
//segment_tree
struct Tree{
    int l,r;
    int head;
}tree[maxm << 2];
struct Edge{
    PII data;
    int next;
}edge[maxm << 2];
int tot,cnt,cnt2;
void add(int u,PII v){
    edge[tot].next = tree[u].head;
    edge[tot].data = v;
    tree[u].head = tot++;
}
void Build(int t,int l,int r){
    tree[t].l = l; tree[t].r = r;
    tree[t].head = -1;
    if(l == r) return;
    int m = (l + r) >> 1;
    Build(t << 1,l,m); Build(t << 1 | 1,m + 1,r);
}
void update(int t,int l,int r,PII v){
    if(l <= tree[t].l && tree[t].r <= r){
        add(t,v);
        return;
    }
    int m = (tree[t].l + tree[t].r) >> 1;
    if(r <= m) update(t << 1,l,r,v);
    else if(l > m) update(t << 1 | 1,l,r,v);
    else{
        update(t << 1,l,m,v);
        update(t << 1 | 1,m + 1,r,v);
    }
}
int find(int x){
    while(fa[x] != -1) x = fa[x];
    return x;
}
void Union(int x,int y){
    x = find(x); y = find(y);
    if(x == y) return;
    if(size[x] > size[y]) swap(x,y);
    Stack[top++] = x;
    fa[x] = y;
    size[y] += size[x] + 1;
}
void rewind(int t){
    while(top > t){
        int x = Stack[--top];
        size[fa[x]] -= size[x] + 1;
        fa[x] = -1;
    }
}
void dfs(int t){
    int now = top;
    for(int i = tree[t].head; ~i; i = edge[i].next){
        PII v = edge[i].data;
        Union(v.fi,v.se);
    }
    if(tree[t].l == tree[t].r){
        while(tot <= cnt2 && q[tot].t == tree[t].l){
            if(find(q[tot].u) == find(q[tot].v)){
                puts("Yes");
            }else{
                puts("No");
            }
            tot++;
        }
        rewind(now);
        return;
    }
    dfs(t << 1); dfs(t << 1 | 1);
    rewind(now);
}
int main(){
    Sca2(N,M); init();
    cnt = 0,cnt2 = 0;
    for(int i = 1; i <= M ; i ++){
        char op[10]; int u,v;
        scanf("%s%d%d",op,&u,&v);
        if(u > v) swap(u,v);
        if(op[0] == ‘Q‘) q[++cnt2] = Query(cnt,u,v);
        else if(op[0] == ‘C‘) line[++cnt] = Line(0,u,v);
        else line[++cnt] = Line(1,u,v);
    }
    tot = 0; Build(1,0,cnt);
    for(int i = 1; i <= cnt; i ++){
        int &x = Q[mp(line[i].u,line[i].v)];
        if(line[i].op == 0) x = i;
        else{
            update(1,x,i - 1,mp(line[i].u,line[i].v));
            x = 0;
        }
    }
    for(map<PII,int>::iterator it = Q.begin(); it != Q.end(); it++){
        pair<PII,int> u = *it;
        if(u.se) update(1,u.se,cnt,u.fi);
    }
    tot = 1;
    dfs(1);
    return 0;
}

原文地址:https://www.cnblogs.com/Hugh-Locke/p/10367480.html

时间: 2024-10-10 20:47:52

bzoj2049 线段树 + 可撤销并查集的相关文章

Codeforces 938G 线段树分治 线性基 可撤销并查集

Codeforces 938G Shortest Path Queries 一张连通图,三种操作 1.给x和y之间加上边权为d的边,保证不会产生重边 2.删除x和y之间的边,保证此边之前存在 3.询问x到y的路径异或最小值 保证图在任意时刻连通 首先连通图路径异或相当于从x到y的任意一条路径再异或上若干个环得到的,只要在dfs过程中把非树边成的环丢到线性基里就好了,其他环一定可以通过这些环异或组合出来 有加边删边操作怎么做呢?线段树时间分治!注意到不能保证在线段树的任意一个节点图是连通的,需要用

Wikioi 2492 树状数组+并查集(单点更新区间查询)

刚开始做的时候用线段树做的,然后就跳进坑里了--因为要开方,所以区间的值都得全部变,然后想用lazy标记的,但是发现用不了,单点更新这个用不了,然后就不用了,就T了.然后实在不行了,看了别人的题解,原来是用树状数组+并查集的方法,唉--没想到啊! 因为开方之后多次那个数就会变成1了,所以是1的时候开方下去就没用了.树状数组更新的时候就把其更新的差更新即可,太机智了这题-- 昨天做了,然后出错找了好久都找不出来,原来是把s[i]写成c[i]了,然后答案一直错,晕-- #include <iostr

ACM1558两线段相交判断和并查集

Segment set Problem Description A segment and all segments which are connected with it compose a segment set. The size of a segment set is the number of segments in it. The problem is to find the size of some segment set. Input In the first line ther

2017端午欢乐赛——Day1T3(树的直径+并查集)

//前些天的和jdfz的神犇们联考的模拟赛.那天上午大概是没睡醒吧,考场上忘了写输出-1的情况,白丢了25分真是**. 题目描述     小C所在的城市有 n 个供电站,m条电线.相连的供电站会形成一个供电群,那么为了节省材料,供电群是一棵树的形式,也即城市是一个森林的形式(树:V个点,V-1条边的无向连通图,森林:若干棵树).每个供电群中不需要所有供电站都工作,最少只需要一个工作,其余的就都会通过电线收到电,从而完成自己的供电任务.当然,这样会产生延迟.定义两个供电站的延迟为它们之间的电线数量

hdu 5458 Stability(树链剖分+并查集)

Stability Time Limit: 3000/2000 MS (Java/Others)    Memory Limit: 65535/102400 K (Java/Others)Total Submission(s): 1347    Accepted Submission(s): 319 Problem Description Given an undirected connected graph G with n nodes and m edges, with possibly r

BZOJ 3211 花神游历各国 (树状数组+并查集)

题解:首先,单点修改求区间和可以用树状数组实现,因为开平方很耗时间,所以在这个方面可以优化,我们知道,开平方开几次之后数字就会等于1 ,所以,用数组记录下一个应该开的数,每次直接跳到下一个不是1的数字进行开平方,至于这个数组,可以用并查集维护. #include <cstdio> #include <cmath> #include <iostream> using namespace std; typedef long long LL; LL c[100005]; in

花神游历各国 题解(小清新线段树/树状数组+并查集)

众所周知,这是一道小清新线段树 然而可以用树状数组水过去且跑得飞快 看到区间开方第一反应肯定是线段树懒标记区间修改之类的,但是这个东西似乎确凿不可维护 所以考虑暴力循环单点修改->T飞 于是我们关注一下开方本身的特殊性 我们知道,如果每次向下取整,一个数经过多次操作最终会变成1(或0) 事实上,大概经过 log(logx)次就会变成1 这是什么概念呢?经过博主测试,1e9只要经过五次开方取整就会变成1 那么接下来就能够利用1每次不必再操作优化复杂度 可以维护一个类似链表的结构,指向下一个>1的

BZOJ 3211 花神游历各国 树状数组+并查集

题目大意:花神对每一个国家有一个喜爱程度,有的时候他会对连续的一段国家进行访问,求他的喜爱程度的和:有的时候他会对连续的一段国家产生厌恶,喜爱程度变成sqrt(x)下取整. 思路:乍一看好像是RMQ问题,用线段树就可以水过,但是开根号的标记怎么下传?这是一个严重的问题,所以我们要换一个思路. 注意到开根号有一个有趣的性质:sqrt(1) = 1,sqrt(0) = 0,而且所有的数字经过有限次的开根号运算都会变成1.这个性质就很好了.我们对每一个点暴力开根号,然后当这个店的点权变成1的时候就打一

bzoj3694: 最短路(树链剖分/并查集)

bzoj1576的帮我们跑好最短路版本23333(双倍经验!嘿嘿嘿 这题可以用树链剖分或并查集写.树链剖分非常显然,并查集的写法比较妙,涨了个姿势,原来并查集的路径压缩还能这么用... 首先对于不在最短路径树上的边x->y,设t为最短路径树上lca(x,y),则t到y上的路径上的点i到根的距离都可以用h[x]+dis[x][y]+h[y]-h[i](h[]为深度)来更新,因为h[i]一定,只要让h[x]+dis[x][y]+h[y]最小就行,这里用树剖直接修改整条链上的数,就可以过了. 并查集的