原题链接
题意
动态加边,单点修改点权,查询两点 x , y x,y x,y 间割点权值和。强制在线。
思路
动态加边,想到使用 LCT。求割点有个很优美的数据结构:圆方树。
具体地,若该次加边后出现了一个环,则新建一个方点,暴力删除环上所有边,并将环上所有点连向方点。
查询时,分离出 x → y x\rightarrow y x→y 这条路径的信息,发现路径上所有圆点都是割点,因此查询权值和并清空该路径上所有的点权即可。
具体细节请看代码注释。
代码
#include <iostream>
#define int long long
using namespace std;
const int N = 1000010;
struct Splay_Node
{
int s[2], p, v;
int sum, rev, cov;
}tr[N];
int n, m, la, cnt;
int stk[N], tmp[N], idx;
inline const void decode(int &x)
{
x ^= la % n;
if (x > n) x %= n;
if (!x) x = 1;
}
inline void pushrev(int x)
{
swap(tr[x].s[0], tr[x].s[1]);
tr[x].rev ^= 1;
}
inline void pushcov(int x)
{
tr[x].v = tr[x].sum = 0;
tr[x].cov = 1;
}
inline void pushup(int x)
{
tr[x].sum = tr[tr[x].s[0]].sum + tr[x].v + tr[tr[x].s[1]].sum;
}
inline void pushdown(int x)
{
if (tr[x].rev)
{
pushrev(tr[x].s[0]), pushrev(tr[x].s[1]);
tr[x].rev ^= 1;
}
if (tr[x].cov)
{
pushcov(tr[x].s[0]), pushcov(tr[x].s[1]);
tr[x].cov = 0;
}
}
inline bool is_root(int x)
{
int p = tr[x].p;
return tr[p].s[0] != x && tr[p].s[1] != x;
}
inline void rotate(int x)
{
int y = tr[x].p, z = tr[y].p;
int k = tr[y].s[1] == x;
if (!is_root(y)) tr[z].s[tr[z].s[1] == y] = x;
tr[x].p = z;
tr[y].s[k] = tr[x].s[k ^ 1], tr[tr[x].s[k ^ 1]].p = y;
tr[x].s[k ^ 1] = y, tr[y].p = x;
pushup(y), pushup(x);
}
inline void splay(int x)
{
int top = 0, r = x;
stk[ ++ top] = r;
while (!is_root(r)) stk[ ++ top] = tr[r].p, r = tr[r].p;
while (top) pushdown(stk[top -- ]);
while (!is_root(x))
{
int y = tr[x].p, z = tr[y].p;
if (!is_root(y))
if ((tr[y].s[1] == x) ^ (tr[z].s[1] == y)) rotate(x);
else rotate(y);
rotate(x);
}
}
void flatten(int u) // 中序遍历,储存路径上点的编号
{
pushdown(u);
if (tr[u].s[0]) flatten(tr[u].s[0]);
tmp[ ++ idx] = u;
if (tr[u].s[1]) flatten(tr[u].s[1]);
}
inline int access(int x)
{
int z = x, y;
for (y = 0; x; y = x, x = tr[x].p)
splay(x), tr[x].s[1] = y, pushup(x);
splay(z);
return y;
}
inline void make_root(int x)
{
access(x);
pushrev(x);
}
inline int find_root(int x)
{
access(x);
while (tr[x].s[0]) pushdown(x), x = tr[x].s[0];
splay(x);
return x;
}
inline bool judge(int x, int y) // 判断 x, y 是否连通
{
make_root(x);
return find_root(y) == x;
}
inline bool link(int x, int y)
{
if (judge(x, y)) return false; // 已经连通
else tr[x].p = y;
return true;
}
inline void split(int x, int y)
{
make_root(x);
access(y);
}
signed main()
{
int op, a, b;
scanf("%lld%lld", &n, &m), cnt = n;
while (m -- )
{
scanf("%lld%lld%lld", &op, &a, &b);
decode(a), decode(b);
if (op == 1)
{
if (a == b || link(a, b)) continue; // 这里特判一下子自环
split(a, b); // 分离出路径,此时 b 为 Splay 的根
idx = 0, cnt ++ ; // 新建方点
flatten(b); // 中序遍历
while (idx)
{
int u = tmp[idx -- ];
tr[u].p = cnt, tr[u].s[0] = tr[u].s[1] = 0;
// 全部连向方点
pushup(u); // 由于改变了结构,所以更新一下信息
}
}
else if (op == 2) splay(a), tr[a].v += b, pushup(a);
// 单点修改,直接改这个节点信息即可。别忘了 pushup。
else
{
if (judge(a, b)) split(a, b), printf("%lld\n", (la = tr[b].sum)), pushcov(b);
// 如果已经连通,则输出答案并清空点权
else printf("%lld\n", (la = 0));
}
}
return 0;
}