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[l−1],那么断开时我们只需要将当前点加上
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();
}
}