BZOJ3514

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"><span style="white-space:pre">	</span>好久没有写动态树了,都快忘了,今天来复习一下</span>

题意:给定N个点M条边的图,询问选第L到R条边时该图的联通块个数。

我就直接讲做法了:以每条边的编号为每条边的边权,然后用LCT维护最大生成树,对于当前的一条边x->y,如果x和y不联通,那么把x和y加入生成树,如果x和y联通,那么在x到y的路径上选取一条权值最小的边,砍了,然后连接x和y,此时记录每条边弹出的边的编号(记为num),如果不连通就为0,如果是自环就忽略。然后在询问的时候,查询L到R之间小于L的num的个数,这个用树套树或者用可持久化线段树维护一下就好了,然后n - ans就是每组询问的答案,至于为什么是这么做,请读者自行思考,这应该是很简单的。

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXX = 400005;
int x, y, i, j, k, n, m, qq;
int lc[MAXX], rc[MAXX], fa[MAXX], sum[MAXX], sum1[MAXX], cnt[MAXX], key[MAXX], num[MAXX], next[2][MAXX];
int ls[MAXX << 4], rs[MAXX << 4], sum2[MAXX << 4], len = 0, ans, root[MAXX], ty;
int q[MAXX];
inline int get()
{
    char c;
    while ((c = getchar()) < 48 || c > 57);
    int res = c - 48;
    while ((c = getchar()) >= 48 && c <= 57)
    res = res * 10 + c - 48;
    return res;
}
inline int isroot(int x)
{
    return ((lc[fa[x]] == x) || (rc[fa[x]] == x));
}
inline void rev(int x)
{
    cnt[x] ^= 1;
    swap(lc[x], rc[x]);
}
inline void putdown(int x)
{
    if (cnt[x])
    {
        cnt[x] = 0;
        if (lc[x]) rev(lc[x]);
        if (rc[x]) rev(rc[x]);
    }
}
inline void pushup(int x)
{
    sum1[x] = x;
    sum[x] = key[x];
    if (lc[x] && sum[lc[x]] < sum[x]) sum[x] = sum[lc[x]], sum1[x] = sum1[lc[x]];
    if (rc[x] && sum[rc[x]] < sum[x]) sum[x] = sum[rc[x]], sum1[x] = sum1[rc[x]];
}
inline void turn(int x)
{
    int y = fa[x], z = fa[y], b = 0;
    if (x == lc[y]) b = rc[x];
    else b = lc[x];
    if (b) fa[b] = y;
    fa[x] = z; fa[y] = x;
    if (z)
        if (lc[z] == y) lc[z] = x;
        else if (rc[z] == y) rc[z] = x;
    if (lc[y] == x) rc[x] = y, lc[y] = b;
    else lc[x] = y, rc[y] = b;
    pushup(y);
}
inline void splay(int x)
{
    int len = 0, i;
    for(i = x; isroot(i); i = fa[i])
    q[++len] = i;
    q[++len] = i;
    while (len) putdown(q[len--]);
    while (isroot(x))
    {
        if (isroot(fa[x]))
            if ((lc[fa[x]] == x) == (lc[fa[fa[x]]] == fa[x])) turn(fa[x]);
            else turn(x);
        turn(x);
    }
    pushup(x);
}
inline void lct_access(int x)
{
    int sb = 0;
    while (x)
    {
        splay(x);
        rc[x] = sb;
        sb = x;
        x = fa[x];
    }
}
inline void makeroot(int x)
{
    lct_access(x);
    splay(x);
    rev(x);
}
inline void LINK(int x, int y)
{
    makeroot(x); fa[x] = y;
}
inline void lct_cut(int x, int y)
{
    makeroot(x);
    lct_access(y); splay(y);
    fa[x] = lc[y] = 0;
    pushup(y);
}
inline int check(int x, int y)
{
    int xx = x, yy = y;
    lct_access(xx); splay(xx);
    while (lc[xx]) xx = lc[xx];
    lct_access(yy); splay(yy);
    while (lc[yy]) yy = lc[yy];
    return (xx == yy);
}
inline void split(int x, int y)
{
    makeroot(y);
    lct_access(x);
    splay(x);
}
inline void insert(int &k, const int &y, const int &p, const int &q, const int &w)
{
    k = ++len;
    sum2[k] = sum2[y];
    ls[k] = ls[y];
    rs[k] = rs[y];
    sum2[k]++;
    if (p == q) return;
    int mid = (p + q) >> 1;
    if (mid >= w) insert(ls[k], ls[y], p, mid, w);
    else insert(rs[k], rs[y], mid + 1, q, w);
}
inline void find(const int &x, const int &y, const int &p, const int &q, const int &l, const int &r)
{
    if (p >= l && q <= r)
    {
        ans += sum2[x] - sum2[y];
        return;
    }
    int mid = (p + q) >> 1;
    if (mid >= l) find(ls[x], ls[y], p, mid, l, r);
    if (mid < r) find(rs[x], rs[y], mid + 1, q, l, r);
}
int main()
{
    cin >> n >> m >> qq >> ty;
    for(i = 1; i <= n; i ++)
    key[i] = m + 1;
    for(i = 1; i <= m; i ++)
    {
        x = get(); y = get();
        key[i + n] = i;
        next[0][i + n] = x;
        next[1][i + n] = y;
        if (x == y) {num[i] = m + 1; continue;}
        if (!check(x, y)) LINK(x, i + n), LINK(i + n, y);
        else{
            split(x, y);
            num[i] = sum[x];
            k = sum1[x];
            lct_cut(next[0][k], k);
            lct_cut(next[1][k], k);
            LINK(x, i + n);
            LINK(i + n, y);
        }
    }
    for(i = 1; i <= m; i ++)
    insert(root[i], root[i - 1], 0, m + 1, num[i]);
    m++;
    for(i = 1; i <= qq; i ++)
    {
        x = get(); y = get();
        if (ty) x ^= ans, y ^= ans;
        if (x > y) swap(x, y);
        ans = 0;
        find(root[y], root[x - 1], 0, m, 0, x - 1);
        printf("%d\n", ans = n - ans);
    }
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值