【BZOJ2759】一道动态树的好题

2759: 一个动态树好题

Time Limit: 10 Sec Memory Limit: 128 MB
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

确实是道动态树的好题啊= =
以下p=10007。
如果我们把每个i看作是一个点,p(i)看作是i的父亲,显然整个方程组会形成一个基环森林,而每个连通块都是一棵基环外向树,根据经验,我们可以拆掉环上的某条边,因此我们便得到了一个森林,便转化为了动态树的问题(ZGY神犇说可以用仙人掌来做,可是我不会= =)。
首先考虑如何回答询问。显然手动拆掉的那条边的起点是整棵树的根,我们可以对根记一个special father(以下简记为s)表示拆掉那条边的终点,我们设s的值为x,那么对于每个点我们都可得出它的值与x的线性关系。我们找到s后,实际上我们得到了s与它自己的线性关系,即我们得到了一个形如 xkx+b(mod p) 的同余方程。那么当k=1时,若b=0则有多组解,反之则无解。其它的,由于p是质数,使用数论知识稍加分析可得该同余方程有且仅有一个解,直接使用扩展欧几里德算法求解即可。注意讨论k=0的情况,因为C语言中负数的除法的特性会使扩展欧几里德算法失效。
修改的话,对于k和b的话access+splay直接改就可以了;修改父亲的时候要分多种情况讨论:如果这个点就是根,那么直接修改它的special father;反之,如果这个点原本在环上,此时我们需要把那条去掉的边补上。当它与它的父亲的边切掉后,它暂时成为了根,与新的父亲连接的时候,如果他们在一棵树上,便形成了一个环,赋值special father即可,否则直接连接。

#include<iostream>
#include<cstdio>
#define maxn 30000
using namespace std;
const int p=10007;
struct data{
    int k,b;
    data(){k=1,b=0;}
    data(int k,int b):k(k),b(b){}
    int cal(int x){return (k*x+b)%p;}
};
data operator+(data a,data b){
    data ret;
    ret.k=a.k*b.k%p;
    ret.b=(a.b*b.k%p+b.b)%p;
    return ret;
}
void exgcd(int a,int b,int &x,int &y){
    if(!b)x=1,y=0;
    else{
        exgcd(b,a%b,y,x);
        y-=a/b*x;
    }
}
struct linkcuttree{
    int ch[maxn+10][2],fa[maxn+10],sfa[maxn+10];
    data val[maxn+10],sum[maxn+10];
    int rc(int fa,int o){return ch[fa][1]==o;}
    int top(int o){return ch[fa[o]][0]!=o&&ch[fa[o]][1]!=o;}
    void pushup(int o){sum[o]=sum[ch[o][0]]+val[o]+sum[ch[o][1]];}
    int vis[maxn+10],ins[maxn+10];
    void dfs(int u){
        ins[u]=vis[u]=1;
        int v=fa[u];
        if(ins[v]){
            fa[u]=0;
            sfa[u]=v;       
        }
        if(!vis[v])dfs(v);
        ins[u]=0;
    }
    void init(int n){
        for(int i=1;i<=n;i++){
            int k,b;
            scanf("%d%d%d",&k,&fa[i],&b);
            val[i]=sum[i]=data(k,b);
        }
        for(int i=1;i<=n;i++)if(!vis[i])dfs(i);
    }
    void rot(int o){
        int f=fa[o],r=rc(f,o);
        if(!top(f))fa[ch[fa[f]][rc(fa[f],f)]=o]=fa[f];
        else fa[o]=fa[f];
        fa[ch[f][r]=ch[o][r^1]]=f;
        fa[ch[o][r^1]=f]=o;
        pushup(f);
        pushup(o);
    }
    void splay(int o){
        while(!top(o)){
            if(!top(fa[o])&&rc(fa[fa[o]],fa[o])==rc(fa[o],o))rot(fa[o]);
            rot(o);
        }
    }
    void access(int u){
        int v=0;
        while(u){
            splay(u);
            ch[u][1]=v;
            pushup(u);
            v=u;
            u=fa[u];
        }
    }
    int findroot(int u){
        access(u);
        splay(u);
        int ro=u;
        while(ch[ro][0])ro=ch[ro][0];
        splay(ro);
        return ro;
    }
    int query(int u){
        access(u);
        splay(u);
        data v1=sum[u];
        int ro=findroot(u),f=sfa[ro];
        access(f);
        splay(f);
        data v2=sum[f];
        if(v2.k==1)return v2.b?-1:-2;
        if(v2.k==0)return v1.cal(v2.b);
        int x,y;
        exgcd(v2.k-1,p,x,y);
        return v1.cal((p-x)%p*v2.b%p);
    }
    void cut(int u){
        access(u);
        splay(u);
        fa[ch[u][0]]=0;
        ch[u][0]=0;
        pushup(u);
    }
    void join(int u,int f){
        access(u);
        splay(u);
        fa[u]=f;
    }
    int oncirclr(int u,int ro){
        int f=sfa[ro];
        if(u==f)return 1;
        access(f);
        splay(f);
        splay(u);
        return !top(f);
    }
    void update(int u,int f,int k,int b){
        access(u);
        splay(u);
        val[u]=data(k,b);
        pushup(u);
        int ro=findroot(u);
        if(u==ro){
            int rf=findroot(f);
            if(rf==ro)sfa[u]=f;
            else{
                sfa[u]=0;
                join(u,f);
            }
        }else{
            if(oncirclr(u,ro)){
                cut(u);
                join(ro,sfa[ro]);
                sfa[ro]=0;
                int rf=findroot(f);
                if(rf==u)sfa[u]=f;
                else join(u,f);
            }else{
                cut(u);
                int rf=findroot(f);
                if(rf==u)sfa[u]=f;
                else join(u,f);
            }   
        }
    }
}lct;
int main(){
    int n;
    scanf("%d",&n);
    lct.init(n);
    int q;
    scanf("%d",&q);
    char e[2];
    int x,k,b,f;
    while(q--){
        scanf("%s%d",e,&x);
        if(e[0]=='A')printf("%d\n",lct.query(x));
        else{
            scanf("%d%d%d",&k,&f,&b);
            lct.update(x,f,k,b);
        }
    }
    return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值