bzoj2759: 一个动态树好题 LCT维护环套树森林,乘法逆元

bzoj2759: 一个动态树好题

Description

有N个未知数x[1…n]和N个等式组成的同余方程组:
x[i]=k[i]*x[p[i]]+b[i] mod 10007
其中,k[i],b[i],x[i]∈[0,10007)∩Z
你要应付Q个事务,每个是两种情况之一:
一.询问当前x[a]的解
A a
无解输出-1
x[a]有多解输出-2
否则输出x[a]
二.修改一个等式
C a k[a] p[a] b[a]

Input

N
下面N行,每行三个整数k[i] p[i] b[i]
Q
下面Q行,每行一个事务,格式见题目描述

Output

对每个询问,输出一行一个整数。
对100%的数据,1≤N≤30000,0≤Q≤100000,时限2秒,其中询问事务约占总数的80%

Sample Input

5
2 2 1
2 3 2
2 4 3
2 5 4
2 3 5
5
A 1
A 2
C 5 3 1 1
A 4
A 5

Sample Output

4276
7141
4256
2126

分析

排个队形。

这是一道真动态树好题,不像某些其它自称好题的题目(bzoj4300“绝世好题”,其实是大水题)

这里要介绍一个新的算法,LCT维护环套树森林。
由于环套树中每个点给的父边是固定的,所以修改操作一定伴随一个删除一个加入。
这个时候有一个神奇的不换根的做法。
首先考虑环,我们先把每个点和其父亲 L i n k Link Link起来,这个时候,如果两个点已经在同一个联通块内,设置一个 s p e c a i l _ f a t h e r specail \_ father specail_father表示环上作为根结点的那个结点连到的结点(暂且称之为老王结点)。

Cut操作

如果切掉的结点刚好是“老王结点”,可以直接把那个结点的 s p e c a i l _ f a t h e r specail \_ father specail_father清零。有一种很巧妙的不换根的操作。 A c c e s s Access Access之后 S p l a y Splay Splay,剩下的结点一定在那个结点的左子树内,直接断边即可。

void Cut(int u, int v) {
    if(v == sf[u]) sf[u] = 0;
    else Access(u),  ch[u][0] = fa[ch[u][0]] = 0, Up(u);
}
Link操作

如上描述,如果两个节点在同一个联通块内,那么新开一个“老王结点”即可,否则的话 A c c e s s Access Access S p l a y Splay Splay之后直接 L i n k Link Link即可。

void Link(int u, int v) {
    if(!v) return;
    if(F(u) == F(v)) sf[u] = v;
    else sf[u] = 0, Access(u), fa[u] = v;
}

Modify操作

A c c e s s Access Access一下,再找到环上的根,分两种情况考虑。如果断的是环上的边,把根和“老王结点连起来”,否则的话断开直接连。这样子还能保证 L i n k Link Link的正确性。因为每次 L i n k Link Link的一定是某棵树的根节点,所以不需要换根操作。

void M(int u, int p, int np, D a) {
    Access(u); v[u] = a; Up(u);
    int rt = u; for(;ch[rt][0];) rt = ch[rt][0];
    Cut(u, p); Link(rt, sf[rt]); Link(u, np);
}

考虑本题,把 i i i p [ i ] p[i] p[i]连起来,就是一个环套树森林。每个节点记录系数。维护的时候代入消元。询问的时候找到根,然后处理环的信息。再推回当前结点的信息即可。

代码

#include<cstdio>
#include<algorithm>
const int N = 3e4 + 10, P = 1e4 + 7;
int ri() {
    char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
    for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int ch[N][2], fa[N], sf[N], iv[P], p[N];
struct D {int k, b; D(int _k = 0, int _b = 0) : k(_k), b(_b) {}}s[N], v[N];
D operator + (D a, D c) {return D(a.k * c.k % P, (c.k * a.b + c.b) % P);}
bool wh(int p) {return ch[fa[p]][1] == p;}
bool Ir(int p) {return ch[fa[p]][wh(p)] != p;} 
void Up(int p) {
    s[p] = v[p];
    if(ch[p][0]) s[p] = s[ch[p][0]] + s[p];
    if(ch[p][1]) s[p] = s[p] + s[ch[p][1]];
}
void Rotate(int p) {
    int f = fa[p], g = fa[f], c = wh(p);
    if(!Ir(f)) ch[g][wh(f)] = p; fa[p] = g;
    ch[f][c] = ch[p][c ^ 1]; if(ch[f][c]) fa[ch[f][c]] = f;
    ch[p][c ^ 1] = f; fa[f] = p; Up(f);
}
void Splay(int p) {
    for(;!Ir(p); Rotate(p))
        if(!Ir(fa[p])) Rotate(wh(fa[p]) == wh(p) ? fa[p] : p);
    Up(p);
}
void Access(int x) {
    for(int pr = 0, p = x;p; pr = p, p = fa[p])
        Splay(p), ch[p][1] = pr, Up(p);
    Splay(x);
}
int F(int p) {for(;fa[p];) p = fa[p]; return p;}
void Link(int u, int v) {
    if(!v) return;
    if(F(u) == F(v)) sf[u] = v;
    else sf[u] = 0, Access(u), fa[u] = v;
}
void Cut(int u, int v) {
    if(v == sf[u]) sf[u] = 0;
    else Access(u),  ch[u][0] = fa[ch[u][0]] = 0, Up(u);
}
void M(int u, int p, int np, D a) {
    Access(u); v[u] = a; Up(u);
    int rt = u; for(;ch[rt][0];) rt = ch[rt][0];
    Cut(u, p); Link(rt, sf[rt]); Link(u, np);
}
 
int Work(int a, int b) {
    if(!a) return !b ? -2 : -1;
    a < 0 ? a = -a : b = P - b;
    return iv[a] * b % P;
}
int Q(int u) {
    int rt = F(u); for(;ch[rt][0];) rt = ch[rt][0]; Splay(rt);
    int y = sf[rt]; Access(y);
    int r = Work(s[y].k - 1, s[y].b);
    if(r < 0) return r;
    r = (r * v[rt].k + v[rt].b) % P;
    if(u == rt) return r;
    Access(u); Splay(rt);
    y = ch[rt][1]; return (s[y].k * r + s[y].b) % P;
}
int main() {
    int n = ri();
    iv[1] = 1; for(int i = 2;i < P; ++i) iv[i] = (P - P / i) * iv[P % i] % P;
    for(int i = 1;i <= n; ++i) v[i].k = ri(), p[i] = ri(), v[i].b = ri(), s[i] = v[i];
    for(int i = 1;i <= n; ++i) Link(i, p[i]);
    for(int q = ri(); q--;) {
        char op = getchar(); for(;op != 'A' && op != 'C'; op = getchar()) ;
        int x = ri(), np; D a;
        if(op == 'A') printf("%d\n", Q(x));
        else a.k = ri(), np = ri(), a.b = ri(), M(x, p[x], np, a), p[x] = np;
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值