BZOJ 2759 一道动态树的好题

一道好题的标准:
从常规的知识点中敲击出新火花,但并不是偏题。
不卡常,不卡溢出……

首先看一个弱化的问题,一个固定的 n n项模方程怎么求解每个变量。高斯消元? 太慢啦!! 
如果我们把 xpx 连边,我们可以得到一个基环森林。对于每个联通块,找到环并求出环中某一个变量的值,显然当一个变量的值求出来了,整个联通块就Okay啦。

接下来我们考虑修改操作,考虑到这玩意是个基环森林,一个常见的思路是拆环成树。所以我们考虑用 LCT 来维护一些信息。
但当务之急是如何处理我们忽略掉的那些边!我们让每个联通块的根节点是这种边的某一端点,然后每个根节点记录这条边的另一端点, 我们叫他 SpecialFather
Splay 中每条树链 ( xy ,其中 x 的深度较浅) 维护一个二元组(k , b):
f=xfa

y=f×k+b

就是如何用 x 的父亲表示y。(其中根节点的父亲就是那个神奇的SpecialFather

那么不难想到如果用将根节点和 SpecialFather 的树链调出来,那么就可以求出 SpecialFather  的值啦!

其实 SpecialFather 的存在并不影响所有的 LCT 的操作。但是要注意在修改边的时候,要先删边再加边,这样逻辑更清晰。

几点启发:
1. 任何对父亲或者自身的操作之前,都需要 Locate
2. LCT 记录的量应该只和点权与树链本身有关,而不应该与此时的根节点有任何联系。
3. 在需要利用根节点信息的时候,我们所有操作都不能 makeRoot

#include <bits/stdc++.h>

using namespace std;
const int maxn = 110000;
const int modu = 10007;

struct line
{
    int k , b;
    line(int k=1 , int b=0):k(k),b(b){}
    line operator +(const line& b)
    {
        line res;
        res.k = (k * b.k) % modu;
        res.b = (b.b + this->b*b.k) % modu;
        return res;
    }

    int F(int x) { return (k * x + b) % modu; } 
};

struct node
{
    node *ch[2] , *fa , *sfa;
    line sum , num; int c;
    void maintain() { sum = ch[0]->sum + num + ch[1]->sum; }
};

node *null = new node();
node pool[maxn];

bool isRoot(node *o) { return o->fa->ch[0] != o && o->fa->ch[1] != o; }

void rotate(node *&o , int d)
{
    node *k = o->ch[d^1];

    k->fa = o->fa;
    k->ch[d]->fa = o;
    o->fa = k;

    o->ch[d^1] = k->ch[d];
    k->ch[d] = o;
    o->maintain();
    k->maintain();
    o = k;
}

void _splay(node* &o)
{
    if(o->c != -1)
    {
        node *&o2 = o->ch[o->c];
        if(o2->c != -1)
        {
            _splay(o2->ch[o2->c]);
            if(o->c == o2->c) rotate(o , o->c^1); else rotate(o2 , o2->c^1);;
        }
        rotate(o , o->c^1);
    }
}

void splay(node *o)
{
    o->c = -1;
    while(!isRoot(o))
    {
        if(o->fa->ch[0] == o) o->fa->c = 0;
        else o->fa->c = 1;

        o = o->fa;
    }

    _splay(o);
}

node* access(node *x)
{
    node *y = null;
    while(x != null)
    {
        splay(x);
        x->ch[1] = y;
        x->maintain();

        y = x;
        x = x->fa;
    }

    while(y->ch[0] != null) y = y->ch[0];
    return y;
}

int p[maxn] , book[maxn] , cnt;

void dfs(int x)
{
    book[x] = cnt;
    int f = p[x];
    if(book[f] == cnt) pool[x].sfa = pool + f;
    else pool[x].fa = pool + f;

    if(!book[f]) dfs(f);
}

void locate(node *x) { access(x); splay(x); }

int inv(int x)
{
    int res = 1 , n = modu - 2;
    while(n)
    {
        if(n&1) (res *= x) %= modu;
        (x *= x) %= modu;
        n >>= 1;
    }
    return res;
}

int query(node *x)
{
    node *r = access(x);
    node *s = r->sfa;

    locate(s);
    int k = s->sum.k , b = s->sum.b;
    if(k == 1) 
    {
        if(b == 0) return -2;
        else return -1;
    }

    locate(x);
    return x->sum.F( ((modu - b)*inv(k+modu-1)) % modu );
}

void modify(node *x , node *fa , line l)
{
    locate(x);
    x->num = l; x->maintain();

    // first lets cut the past edge
    node *r = access(x);
    if(x == r) x->sfa = null;
    else 
    {
        locate(x);
        x->ch[0]->fa = null;
        x->ch[0] = null; x->maintain();

        if(access(r->sfa) != r) locate(r) , r->fa = r->sfa , r->sfa = null;
    }

    // then build the new one!
    // aparently x is a root of some tree
    if(access(fa) != access(x)) locate(x) , x->fa = fa;
    else locate(x) , x->sfa = fa;
}

int re() { int n; scanf("%d" , &n); return n; }

int main()
{
    #ifndef ONLINE_JUDGE
    freopen("in","r",stdin);
    #endif

    int n;
    cin>>n;

    for(int i=1 , k , b;i<=n;i++)
    {
        scanf("%d%d%d" , &k , p+i , &b);
        pool[i].ch[0] = pool[i].ch[1] = pool[i].fa = pool[i].sfa = null;
        pool[i].sum = pool[i].num = line(k , b);
    }

    for(int i=1;i<=n;i++) if(!book[i]) ++cnt , dfs(i);

    int q;
    cin>>q;
    char op[2];
    while(q--)
    {
        scanf("%s" , op);
        if(op[0] == 'A') printf("%d\n" , query(pool + re()));
        else 
        {
            int x = re() , k = re() , p = re() , b = re();
            modify(pool+x , pool+p , line(k , b));
        }
    }

    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值