2024暑期集训补题(线段树分治 + 可撤销并查集 + 懒标记前缀和维护)

2024杭电多校第一场 1004

在这里插入图片描述
题意:
给定一张图,图上有 m m m条边和 n n n个点,这些边有存在时间,只会在一定时间内出现,问从 1 1 1能到达每一个点的所有时刻总和异或值(对于一个点来说,其能到达这个点的所有时刻总和,然后对所有点的这些时刻总和求异或和)

题解:
对于这种操作存在于一定时间区间内的问题,显然是线段树分治的板子,利用线段树分治将所有时刻的操作存入线段树,每询问到一个叶子节点即一个时刻,在向下递归的同时处理该区间内存在的操作即可,而对于是否能到达也显然用配套的可撤销并查集来处理,可撤销并查集能在回溯时撤销当前节点的操作,因此对于每个时刻,我们都可以求出能到达的点。
但是我们要求的是每个点能到达的时刻,如果暴力处理,最坏可能会达到 O ( n 2 ) O(n^2) O(n2),显然不可取。因此我们需要用一个懒标记来维护时刻的总和。每个时刻 t i t_i ti都对 1 1 1所在的连通块根节点打上值为 t i t_i ti的标记,表示这个时刻 1 1 1所在的连通块所有节点都加上 t i t_i ti。考虑两种情况:
1. 1. 1.连接两个点,实际上在每个时刻, 1 1 1所在的连通块根节点的标记值为时刻的前缀和,因此如果我们要让当前加入的点仅计算后面时刻的贡献,显然让当前的点标记值减去当前时刻 1 1 1所在连通块根节点的标记值。
2. 2. 2.断开两个点,结合 1 1 1操作,显然很容易想到对于一个前缀和数组,如何求原数组的区间总和,即 p r e [ r ] − p r e [ l − 1 ] pre[r] - pre[l - 1] pre[r]pre[l1],那么断开时我们只需要将当前点加上 1 1 1连通块的根节点的标记值即可。

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
#define debug(p) for (auto i : p)cerr << i << " "; cerr << endl;
#define debugs(p) for (auto i : p)cerr << i.first << " " << i.second << endl;
typedef pair<int, int> pll;
string yes = "YES";
string no = "NO";
constexpr int N = 6e5 + 7;
struct Line{
    int u, v, st, ed;
}line[N];
struct DSU{
    int n, tot;
    vector<int>p, siz, stk, pre;
    DSU(){};
    DSU(int temp):n(temp){build(2 * temp);}
    void build(int n)
    {
        p.clear();
        siz.clear();
        stk.clear();
        pre.clear();
        p.resize(n + 1);
        siz.resize(n + 1);
        stk.resize(n + 1);
        pre.resize(n + 1);
        for (int i = 1; i <= n; i++)
        {
            p[i] = i;
            siz[i] = 1;
        }
    }
    int fd(int x)//路径压缩,复杂度O(1)
    {
        if (p[x] != x) p[x]=fd(p[x]);
        return p[x];
    }
    void merge(int a, int b)//常态合并
    {
        if (fd(a) == fd(b))return;
        p[fd(b)] = fd(a);
    }
    int fd2(int x)//按秩合并用,复杂度O(logn)
    {
        if (p[x] != x)return fd2(p[x]);
        return p[x];
    }
    void merge2(int a, int b)//按秩合并
    {
        a = fd2(a), b = fd2(b);
        if(a == b) return;
        if(siz[a] < siz[b]) swap(a, b);
        stk[++tot] = b, p[b] = a, siz[a] += siz[b], pre[b] -= pre[a];
    }
    void back(int del)
    {
        while(del--)
        {
            if(!tot)return;
            int a = stk[tot--];
            siz[p[a]] -= siz[a];
            pre[a] += pre[p[a]];
            p[a] = a;
        }
    }
    void push_lazy(int x, int y)
    {
        pre[x] += y;
    }
}dsu;
struct Seg_tree{
    vector<int>line;
}tr[N * 4];
void modify(int u, int l, int r, int ql, int qr, int x)
{
    if(ql <= l && r <= qr)
    {
        tr[u].line.push_back(x);
        return;
    }
    int mid = l + r >> 1;
    if(ql <= mid)modify(u << 1, l, mid, ql, qr, x);
    if(qr > mid)modify(u << 1 | 1, mid + 1, r, ql, qr, x);
}
void solve(int u, int l, int r)
{
    int last =  dsu.tot;
    for (int i = 0; i < tr[u].line.size(); i++)
        dsu.merge2(line[tr[u].line[i]].u, line[tr[u].line[i]].v);
    if(l == r)
    {
        dsu.push_lazy(dsu.p[1], l);
        dsu.back(dsu.tot - last);
        return;
    }
    int mid = l + r >> 1;
    solve(u << 1, l, mid);
    solve(u << 1 | 1, mid + 1, r);
    dsu.back(dsu.tot - last);
    return;
}
void solve()
{
    int n, m;
    cin >> n >> m;
    dsu.build(n);
    for (int i = 1; i <= m; i++)
    {
        cin >> line[i].u >> line[i].v >> line[i].st >> line[i].ed;
        modify(1, 1, n, line[i].st, line[i].ed, i);
    }
    solve(1, 1, n);
    int ans = 0;
    for (int i = 1; i <= n; i++)ans ^= dsu.pre[i];
    cout << ans << endl;

}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int T = 1;
    // cin >> T;
    while(T--)
    {
        solve();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值