CC FIBTREE Fibonacci Numbers on Tree

20 篇文章 0 订阅
14 篇文章 0 订阅

题面在这里

题意:

给一棵树,有4种操作:
1.询问以x为根时y子树内的点权和。
2.询问x~y链上的点权和。
3.将x~y这条链展开,第i个点加上 fibi ,其中 fibi 表示斐波那契第i项, fib1=fib2=1
4.恢复到第x个操作时的状态。
本题强制在线。

做法:

首先 fibi=15×((1+52)i(152)i)
然后 5 在模 1e9+9 下存在(等于383008016,当作一个常数就好qwq)
然后原问题可以转化为区间加上一个等比数列。
由于等比数列的公比相同,我们只需维护一下区间的首项,首项可以不停累加,公比不会改变。
于是可以用线段树实现。

然后这道题的另外一些操作,就套上树链剖分,套上可持久化,就可以实现了。
代码细节比较恶心,写了很多的注释,应该可以看懂。

代码:

/*************************************************************
    Problem: codechef FIBTREE Fibonacci Numbers on Tree
    User: fengyuan
    Language: C++
    Result: Accepted
    Time: 670 ms
    Memory: 1147.4 MB
    Submit_Time: 2017-12-28 11:02:06
*************************************************************/

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#define sqrt5 383008016//根号5在%1e9+9的意义下的值
#define mid ((l+r)/2)
using namespace std;

const int N = 100010, M = 40000005;
const int MOD = 1e9 + 9;
int n, Q, cnt, clk, now, inv_sqrt5, total;
int head[N], depth[N], sz[N], f[N][19], son[N], top[N], in[N], out[N];
int v[2], s[N][2], spre[N][2];
int rt[N], p[M][2][2], L[M], R[M], sum[M];
struct Edge{ int to, nex; }e[N<<1];

inline int ksm(int x, int p)
{
    int ret = 1;
    while(p) { if(p&1) ret = 1LL*ret*x%MOD; x = 1LL*x*x%MOD; p >>= 1; }
    return ret;
}
inline void prepare()
{
    v[0] = 1LL*(1+sqrt5)*ksm(2, MOD-2)%MOD;//v[0] = (1+根号5)/2
    v[1] = 1LL*(1-sqrt5+MOD)*ksm(2, MOD-2)%MOD;//v[1] = (1-根号5)/2
    inv_sqrt5 = ksm(sqrt5, MOD-2);//inv_sqrt5是根号5的逆元
    for(int i = 0; i <= n; i ++) s[i][0] = ksm(v[0], i), s[i][1] = ksm(v[1], i);//s[i][0/1]存v[0/1]的i次方
    spre[0][0] = s[0][0]; spre[0][1] = s[0][1];//spre存s的前缀和
    for(int i = 1; i <= n; i ++) spre[i][0] = (spre[i-1][0]+s[i][0])%MOD, spre[i][1] = (spre[i-1][1]+s[i][1])%MOD;
}
inline void addEdge(int x, int y){ e[++ cnt].to = y; e[cnt].nex = head[x]; head[x] = cnt; }
inline void dfs(int u, int lst, int s)
{
    f[u][0] = lst; depth[u] = s; sz[u] = 1;
    for(int i = head[u]; i; i = e[i].nex) {
        int v = e[i].to; if(v == lst) continue;
        dfs(v, u, s+1); sz[u] += sz[v];
        if(!son[u] || sz[v] > sz[son[u]]) son[u] = v;
    }
}
inline void dfs2(int u, int t)
{
    top[u] = t; in[u] = ++ clk;
    if(son[u]) dfs2(son[u], t);
    for(int i = head[u]; i; i = e[i].nex) {
        int v = e[i].to; if(v == f[u][0] || v == son[u]) continue;
        dfs2(v, v);
    } out[u] = clk;
}
inline int LCA(int x, int y)//利用轻重链求lca
{
    while(top[x] != top[y]) {
        if(depth[top[x]] < depth[top[y]]) swap(x, y);
        x = f[top[x]][0];
    }
    return depth[x] < depth[y] ? x : y;
}
inline int cal(int x, int y, int z) { int ret = 1LL*x*spre[y-1][z]%MOD; return z ? MOD-ret : ret; }//计算首项为x长度为y的等比数列和
inline void add(int lst, int &k, int l, int r, int x, int y, int tg, int zs)
{
    k = ++ total;
    p[k][0][0] = p[lst][0][0]; p[k][0][1] = p[lst][0][1]; p[k][1][0] = p[lst][1][0]; p[k][1][1] = p[lst][1][1];
    //p的第一个[0/1]表示是(1+根号5)/2还是(1-根号5)/2,第二个[0/1]表示当前区间是递增还是递减
    L[k] = L[lst]; R[k] = R[lst];
    sum[k] = (sum[lst]+cal(s[zs][0], y-x+1, 0))%MOD; sum[k] = (sum[k]+cal(s[zs][1], y-x+1, 1))%MOD;//sum维护区间和
    //printf("%d %d\n", k, sum[k]);
    if(l == x && r == y) {//只有当区间完全覆盖时才将首项累加
        p[k][0][tg] = (p[k][0][tg]+s[zs][0])%MOD;
        p[k][1][tg] = (p[k][1][tg]+s[zs][1])%MOD;
        return;
    }//p维护区间的第一项,由于发现等比数列加上一个公比相同的等比数列,它的公比还是相同,所以可以直接加
    if(y <= mid) add(L[lst], L[k], l, mid, x, y, tg, zs);
    else if(x > mid) add(R[lst], R[k], mid+1, r, x, y, tg, zs);
    else {
        if(!tg) { add(L[lst], L[k], l, mid, x, mid, tg, zs); add(R[lst], R[k], mid+1, r, mid+1, y, tg, zs+mid-x+1); }
        else { add(R[lst], R[k], mid+1, r, mid+1, y, tg, zs); add(L[lst], L[k], l, mid, x, mid, tg, zs+y-mid); }
    } //zs+mid-x+1和zs+y-mid分别是当区间等比数列是递增、递减时,mid+1/mid位子上的次数
}
inline int ask(int k, int l, int r, int x, int y)
{
    if(l == x && r == y || !k) return sum[k];
    //计算出[x,y]区间内等比数列的和
    int ret = cal(1LL*p[k][0][0]*s[x-l][0]%MOD, y-x+1, 0);
    ret = (ret+cal(1LL*p[k][1][0]*s[x-l][1]%MOD, y-x+1, 1))%MOD;
    ret = (ret+cal(1LL*p[k][0][1]*s[r-y][0]%MOD, y-x+1, 0))%MOD;
    ret = (ret+cal(1LL*p[k][1][1]*s[r-y][1]%MOD, y-x+1, 1))%MOD;
    //由于只有在完全覆盖时才累加,所以还需递归向下查询
    if(y <= mid) ret = (ret + ask(L[k], l, mid, x, y))%MOD;
    else if(x > mid) ret = (ret + ask(R[k], mid+1, r, x, y))%MOD;
    else {
        ret = (ret + ask(L[k], l, mid, x, mid))%MOD;
        ret = (ret + ask(R[k], mid+1, r, mid+1, y))%MOD;
    } return ret;
}
inline void add(int x, int y)
{
    int l = LCA(x, y), tmp1 = 1, tmp2 = depth[x]+depth[y]-2*depth[l]+1;//tmp1和tmp2分别维护x-l,y-l当前区间第一个数的次数
    //l是x,y的最近公共祖先,将树链分成两半,一半x-l,一半y-l
    while(top[x] != top[l]) {
        add(rt[now], rt[now], 1, n, in[top[x]], in[x], 1, tmp1);//1表示的是当前区间最左边是最高次
        tmp1 += depth[x] - depth[f[top[x]][0]]; x = f[top[x]][0];
    } add(rt[now], rt[now], 1, n, in[l], in[x], 1, tmp1);
    while(top[y] != top[l]) {
        tmp2 -= depth[y] - depth[f[top[y]][0]];
        add(rt[now], rt[now], 1, n, in[top[y]], in[y], 0, tmp2+1);//2表示的是当前区间最左边是最低次
        y = f[top[y]][0];
    }
    if(y != l) { tmp2 -= depth[y] - depth[l]; add(rt[now], rt[now], 1, n, in[l]+1, in[y], 0, tmp2+1); }
}
inline int find(int x, int y)//找x-y路径上y的儿子
{
    int tmp = depth[x] - depth[y] - 1;
    for(int i = 17; i >= 0; i --) if(tmp>>i&1) x = f[x][i]; return x;
}
inline int qtree(int x, int y)//求x为根,y的子树和
{
    int ret;
    if(x == y) ret = ask(rt[now], 1, n, 1, n);//如果x=y,可以取全部
    else if(in[y] <= in[x] && in[x] <= out[y]) {//如果y是x的祖先,相当于全部减去x-y路径上y的儿子的子树
        ret = ask(rt[now], 1, n, 1, n);
        int tmp = find(x, y);//找x-y路径上y的儿子
        ret = (ret-ask(rt[now], 1, n, in[tmp], out[tmp])+MOD)%MOD;
    } else ret = ask(rt[now], 1, n, in[y], out[y]);//否则就是正常的y的子树
    return 1LL*ret*inv_sqrt5%MOD;
}
inline int qline(int x, int y)//求链和
{
    int ret = 0;
    while(top[x] != top[y]) {
        if(depth[top[x]] < depth[top[y]]) swap(x, y);
        ret = (ret+ask(rt[now], 1, n, in[top[x]], in[x]))%MOD;
        x = f[top[x]][0];
    } if(depth[x] > depth[y]) swap(x, y);
    ret = (ret + ask(rt[now], 1, n, in[x], in[y]))%MOD;
    return 1LL*ret*inv_sqrt5%MOD;
}
int main()
{
    scanf("%d%d", &n, &Q); prepare();
    for(int i = 1; i < n; i ++) {
        int x, y; scanf("%d%d", &x, &y);
        addEdge(x, y); addEdge(y, x);
    }
    dfs(1, 0, 0); dfs2(1, 1);//树链剖分预处理
    for(int j = 1; j <= 17; j ++)
        for(int i = 1; i <= n; i ++) f[i][j] = f[f[i][j-1]][j-1];//倍增预处理
    int lastans = 0;
    for(int i = 1; i <= Q; i ++) {
        int x, y; char opt[5]; scanf("%s%d", opt, &x); x ^= lastans;
        if(opt[0] == 'R') { rt[i] = rt[x]; continue; }//修改时间戳
        rt[i] = rt[i-1]; scanf("%d", &y); now = i;
        if(opt[0] == 'A') { add(x, y); continue; }
        if(opt[1] == 'S') lastans = qtree(x, y); else lastans = qline(x, y);
        printf("%d\n", lastans);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值