BZOJ 2759 一个动态树好题(Link-Cut Tree+数学)

来源:互联网 发布:笑气在淘宝上叫什么 编辑:程序博客网 时间:2024/06/05 10:46

2759: 一个动态树好题

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 772  Solved: 258
[Submit][Status][Discuss]

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

HINT

Source

By 范浩强




        中文题就不解释题意了……
        做一道LCT主要是想在赛前复习一下LCT,然后再次感叹LCT的神奇……
        这题很多同余方程,某个方程的解依赖于其他方程的解,由此可以构成一个类似于树的数据结构,所以比较自然的想到LCT。然后根据式子的关系,显然这些方程是可以相互合并的(把一个方程代入另一个方程),如此更适合用数据结构了。但是,问题来了,这里面可能出现环,该如何处理呢。我们注意到,父亲可以由儿子表示,爷爷可以由父亲表示,那么当出现环的时候,我完全可以通过合并,使得假想根和它的父亲fa(它的父亲也是它的后代)用同一个x表示。于是出现这种情况相当于x=k*x+b,相当于解这样的一个方程,这个就小菜一碟啦。所以说,我们可以选定一个假想根,然后断开它与它的父亲,但是要把它的父亲给记录下来,之后就是一颗完整的树。那么经过这样操作之后,就会形成一个森林。具体来说,我们可以看下面那个图来理解。

                                                                                   
        其中,虚线那条边表示被删掉的边,如此一堆关系可以形成一个森林。
        然后,我们再考虑每一种操作。对于询问方程的结果,我们按照上面说的方法来求解。首先,对于要求的点x,它的解等于k[x]*根的解+b[x]。那么相当于要求根的解,而根的解=k[fa]*根的解+b[fa],这个fa就是我们之前删掉的那个父亲,在这时起作用。解这个方程就可以求出结果。
        接着我们开修改操作。所谓修改主要就是考虑断开和重新连接嘛。这时,我们就要分情况讨论一下了,我们设修改的点为x,与之有关的方程是f。
                如果x根而且f的根不是x,那么其他不管,直接把它的父亲变为f即可;
                如果f的根也是x,这意味这什么呢?意味着构成了一个新的环,所以我们从这断开,并把这个父亲保存下来;
                如果x不是根,那么显然关联方程改变,对应的与父亲的连边肯定是要断开的,然后还要再讨论一下啦,如果x在环上(可以看上图,不是所有点都在环上),那么就会对原来整个环产生影响,原本的环断开变成了真正的链,意味着虚拟断开的边不需要再断开了,把根的父亲变为原本断开的父亲即可;如果x不在环上,那么仅仅断开就行了,其他不影响;
        这便是两种操作。然后关于LCT本身,还有要说的。因为我们保存的元素是方程的两个系数k和b,所以我们在定义的时候定义一个结构重载加法。然后很明显可以发现,这个重载的加法并不满足交换律,所以我们在合并的时候要格外小心,注意顺序。还有,就是LCT的beroot操作不要随便乱用,至少在这题是这样的。具体见代码:
#include<bits/stdc++.h>#define mod 10007#define N 30010using namespace std;struct line{    int k,b;    friend line operator + (const line a,const line b)//重载加法    {return line{a.k*b.k%mod,(b.b+b.k*a.b)%mod};}};int n,m,t,inv[N],rt[N],v[N];struct Link_Cut_Tree{    int son[N][2],fa[N];    line num[N],sum[N];    inline bool which(int x){return son[fa[x]][1]==x;}    bool isroot(int x){return !fa[x]||son[fa[x]][which(x)]!=x;}    inline void push_up(int x)    {        if (!x) return; sum[x]=num[x];        if (son[x][0]) sum[x]=sum[son[x][0]]+sum[x];//注意合并顺序,不能错        if (son[x][1]) sum[x]=sum[x]+sum[son[x][1]];    }    inline void Rotate(int x)    {        int y=fa[x]; bool ch=which(x);        son[y][ch]=son[x][ch^1];son[x][ch^1]=y;        if (!isroot(y)) son[fa[y]][which(y)]=x;        fa[x]=fa[y]; fa[y]=x; fa[son[y][ch]]=y;        push_up(y); push_up(x);    }    inline void splay(int x)    {        while (!isroot(x))        {            int y=fa[x];            if (!isroot(y))            {                if (which(x)^which(y)) Rotate(x);                                  else Rotate(y);            }            Rotate(x);        }    }    inline void access(int x)    {        int y=0;        while (x)        {            splay(x);            son[x][1]=y;            push_up(x);            y=x; x=fa[x];        }    }    inline int getroot(int x)    {        access(x); splay(x);        while (son[x][0]) x=son[x][0];        splay(x); return x;    }    inline void cut(int x)    {        access(x); splay(x);        son[x][0]=fa[son[x][0]]=0;        push_up(x);    }    inline bool judge(int x,int y)//判定x,是否在y为根的环的环上    {        access(rt[y]); splay(rt[y]); splay(x);//如果在环上,那么在access(rt[y])之后再splay(x)        return x==rt[y]||!isroot(rt[y]);//rt[y]一定不是根,或者x本身就是rt[y]    }} LCT;void init(){    inv[1]=1;    for(int i=2;i<N;i++)        inv[i]=(mod-mod/i)*inv[mod%i]%mod;//线性求逆元,解方程要用    memset(rt,0,sizeof(rt));}void dfs(int x){    if (v[x]) return; v[x]=t;    if (v[LCT.fa[x]]==t)    {        rt[x]=LCT.fa[x];        LCT.fa[x]=0;    } else dfs(LCT.fa[x]);}int main(){    init();    scanf("%d",&n);    for(int i=1;i<=n;i++)    {        int k,f,b;        scanf("%d%d%d",&k,&f,&b);        LCT.fa[i]=f;        LCT.num[i]=line{k,b};    }    for(int i=1;i<=n;i++)        if (!v[i]) t++,dfs(i);    scanf("%d",&m);    while(m--)    {        char op[5];        int x,k,f,b,Rt;        scanf("%s",op);        if (op[0]=='A')        {            scanf("%d",&x);            LCT.access(x); LCT.splay(x);            line a=LCT.sum[x]; Rt=LCT.getroot(x);            LCT.access(rt[Rt]); LCT.splay(rt[Rt]);            line b=LCT.sum[rt[Rt]];            if (b.k==1&&a.k!=0)//判定多组解和无解            {                if (b.b) puts("-1");                    else puts("-2");            } else            {                int ans=b.b*inv[(1-b.k+mod)%mod]%mod;//求根的解                printf("%d\n",(a.k*ans+a.b)%mod);//求x的解            }        } else        {            scanf("%d%d%d%d",&x,&k,&f,&b);            LCT.access(x); LCT.splay(x);            LCT.num[x]=line{k,b}; LCT.push_up(x);            Rt=LCT.getroot(x);            if (Rt!=x)//x不为根            {                LCT.cut(x);                if (LCT.judge(x,Rt))//判定x是否在环上                {                    LCT.access(Rt); LCT.splay(Rt);                    LCT.fa[Rt]=rt[Rt]; rt[Rt]=0;//在的话把rt变为真正意义上的fa                }            }            if (LCT.getroot(f)==x) rt[x]=f;//如果f的根与x相同,那么相当于构成了一个新的环                          else LCT.fa[x]=f;        }    }    return 0;}


原创粉丝点击