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;
}