【GDOI2018模拟9.23】动态图

本文介绍了一种使用按秩合并并查集处理大规模数据集的暴力解法,并提出了一种利用链剖分(LCT)和主席树进行优化的算法方案,通过维护最大生成树来高效解决特定类型的查询问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description:

这里写图片描述
1<=n<=10^6,1<=q<=200000

题解:

暴力可以用按秩合并并查集,这个数据结构支持删除。

对于正解,我们需要把l->r条边一次加进并查集,对于每一条边,它有贡献,就是加入它的时候,它会合并两个不同的集合。
反过来,它没有贡献,就是它所连着的两端本来就在一个集合里。

假设我们按1->r的顺序加入每一条边,当加入一条边时,设它是x,y,设z为x到y的路径上编号最小的边(x,y不在一个集合里是0)的编号,那么如果有询问包含这条边,且左端点小于等于z,那么当前这条边就是没有贡献的,否则它就是有贡献。

于是用lct维护一棵最大生成树,就可以求出z,再用主席树随便维护一下就行了。

Code:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define min(a, b) ((a) < (b) ? (a) : (b))
using namespace std;

const int N = 1000000, M = 200005;
int n, q, type, tt, u, v, l, r, ans, m;
int t[N + M][2], fa[N + M], pf[N + M], rev[N + M], mi[N + M], dd[N + M];
int g[N + M], tot;
struct tree{
    int l, r, s;
}d[N * 10];
struct edge {
    int u, v, x, y, z;
}e[N + M];
int last[N + M];

int lr(int x) {return t[fa[x]][1] == x;}
void chan(int x) {if(x) swap(t[x][0], t[x][1]), rev[x] ^= 1;}
void down(int x) {if(x && rev[x]) chan(t[x][0]), chan(t[x][1]), rev[x] = 0;}
void update(int x) {
    if(!x) return;
    mi[x] = min(mi[t[x][0]], mi[t[x][1]]);
    if(x > N) mi[x] = min(mi[x], x - N);
}
void xc(int x) {
    for(; x; x = fa[x]) dd[++ dd[0]] = x;
    for(; dd[0]; dd[0] --) down(dd[dd[0]]);
}
void rotate(int x) {
    int y = fa[x], k = lr(x);
    t[y][k] = t[x][!k]; if(t[x][!k]) fa[t[x][!k]] = y;
    fa[x] = fa[y]; if(fa[y]) t[fa[y]][lr(y)] = x;    
    t[x][!k] = y; fa[y] = x; pf[x] = pf[y];
    update(y); update(x);
}
void splay(int x, int y) {
    xc(x);
    while(fa[x] != y) {
        if(fa[fa[x]] != y)
            if(lr(x) == lr(fa[x])) rotate(fa[x]); else rotate(x);
        rotate(x);
    }
}
void dg(int x) {
    if(x == 0) return;
    down(x);
    printf("%d %d %d %d tree\n", x, mi[x], t[x][0], t[x][1]);
    dg(t[x][0]); dg(t[x][1]);
}
void access(int x) {
    int y = 0;
    while(x) {
        splay(x, 0); fa[t[x][1]] = 0; pf[t[x][1]] = x;
        t[x][1] = y; fa[y] = x; pf[y] = 0;
        update(x); y = x; x = pf[x];
    }    
}
void makeroot(int x) {
    access(x); splay(x, 0); chan(x);
}
void link(int x, int y) {
    makeroot(x); pf[x] = y; access(x);
}
void cut(int x, int y) {
    makeroot(x); access(y); splay(y, 0);
    t[y][0] = fa[x] = pf[x] = 0;
    update(y);
}
int find_z(int x) {
    down(x);
    return t[x][0] == 0 ? x : find_z(t[x][0]);
}

int pd(int x, int y) {
    makeroot(x); access(y); splay(y, 0);
    return find_z(y) == x;
}

void change(int i, int x, int y, int l, int r) {
    if(x == y) {
        d[i].s += r;
        return;
    }
    int m = (x + y) >> 1;
    d[++ tot] = d[d[i].l], d[i].l = tot;
    d[++ tot] = d[d[i].r], d[i].r = tot;
    if(l <= m) change(d[i].l, x, m, l, r); else change(d[i].r, m + 1, y, l, r);
    d[i].s = d[d[i].l].s + d[d[i].r].s;
}

int find(int i, int x, int y, int l, int r) {
    if(i == 0) return 0;
    if(x == l && y == r) return d[i].s;
    int m = (x + y) >> 1;
    if(r <= m) return find(d[i].l, x, m, l, r); else
    if(l > m) return find(d[i].r, m + 1, y, l, r); else
    return find(d[i].l, x, m, l, m) + find(d[i].r, m + 1, y, m + 1, r);
}

const int Mm = 200000;

void xiu(int m, int last) {
    g[m] = ++ tot; d[g[m]] = d[g[m - 1]];
    change(g[m], 0, Mm, last, 1);
}

int qiu(int l, int r) {
    return find(g[r], 0, Mm, 0, l - 1) - find(g[l - 1], 0, Mm, 0, l - 1);
}

int main() {
    freopen("graph.in", "r", stdin);
    freopen("graph.out", "w", stdout);
    scanf("%d %d %d", &n, &q, &type);
    fo(i, 0, N) mi[i] = 1e9;
    g[0] = tot = 1;
    fo(Q, 1, q) {
        scanf("%d", &tt);
        if(tt == 1) {
            scanf("%d %d", &u, &v);
            if(type) u = (u ^ ans) % n + 1; 
            if(type) v = (v ^ ans) % n + 1;
            e[++ m].u = u, e[m].v = v;
            if(u == v) {
                last[m] = m; xiu(m, m);
                continue;
            }
            if(pd(u, v)) {
                makeroot(u); access(v); splay(v, 0);
                int z = mi[v];
                e[m].x = e[z].u; e[m].y = e[z].v; e[m].z = z;
                cut(e[m].x, e[m].z + N); cut(e[m].y, e[m].z + N);
                link(u, m + N); link(v, m + N);
                last[m] = z; xiu(m, z);
            } else {
                link(u, m + N); link(v, m + N);
                last[m] = 0; xiu(m, 0);
            }
        } else
        if(tt == 2) {
            if(e[m].u == e[m].v) {
                m --; continue;
            }
            if(last[m]) {
                cut(e[m].u, m + N); cut(e[m].v, m + N);
                link(e[m].x, e[m].z + N); link(e[m].y, e[m].z + N);
            } else {
                cut(e[m].u, m + N); cut(e[m].v, m + N);
            }
            m --;
        } else {
            scanf("%d %d", &l, &r);
            if(type) l = (l ^ ans) % m + 1;
            if(type) r = (r ^ ans) % m + 1;
            if(l > r) swap(l, r);
            ans = n - qiu(l, r);
            printf("%d\n", ans);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值