牛客网 华华和月月种树(dfs序+区间更新树状数组)

题目链接:https://ac.nowcoder.com/acm/problem/23051

题目描述

华华看书了解到,一起玩养成类的游戏有助于两人培养感情。所以他决定和月月一起种一棵树。因为华华现在也是信息学高手了,所以他们种的树是信息学意义下的。
华华和月月一起维护了一棵动态有根树,每个点有一个权值。刚开存档的时候,树上只有 0 号节点,权值为 0 。接下来有两种操作:
操作 1:输入格式1 i,表示月月氪金使节点 i 长出了一个新的儿子节点,权值为0,编号为当前最大编号 +1(也可以理解为,当前是第几个操作 1,新节点的编号就是多少)。
操作 2:输入格式 2 i a,表示华华上线做任务使节点 i 的子树中所有节点(即它和它的所有子孙节点)权值加 a 。
但是月月有时会检查华华有没有认真维护这棵树,会作出询问:
询问 3:输入格式3 i,华华需要给出 i 节点此时的权值。
华华当然有认真种树了,不过还是希望能写个程序以备不时之需。

输入描述:

第一行一个正整数M,接下来M行,每行先输入一个正整数O表示操作类型,再输入一个非负整数i表示操作或询问的节点编号,如果O=2,再输入一个正整数a。

输出描述:

对于每个询问3,输出一个非负整数表示询问的答案。

 

题意不说了,主要思路就是把树上的区间操作通过dfs序变成普通的区间操作,这样我们就可以直接使用树状数组或线段树来进行操作。首先我们要知道,对一棵树进行dfs,第i个节点的dfs序为DFN_i,那么以某一个节点为根的子树里面的dfs序都是连续的,即范围为(DFN_i,DFN_i+size_i-1),其中size_i以i为根节点的子树的大小。

知道上面的知识之后,我们就可以离线处理把整棵树变成一个大区间,剩下的就是区间处理。我们可以按照给定的操作顺序进行更新,但是只有在节点被加入这棵树的时候,他的权值才可以被更新,所以在它加入之前的操作都是无效的,因此我们在它加入树的时候把它当前已有的权值清零即可。

因为此题只有单点查询,所以我们可用利用树状数组可以区间更新,单点查询的特性,去维护这个区间,具体看代码。

 

#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long

using namespace std;


int n;
vector<int> v[100010];
int dfn[100010];
int sz[100010];
int tree[100010];

struct query
{
    int t,id,a;
}q[400010];


void dfs(int x)
{
    dfn[x] = ++n;
    sz[x] = 1;
    for(int i=0;i<v[x].size();i++)
    {
        int xx = v[x][i];
        dfs(xx);
        sz[x] += sz[xx];
    }
}


void add(int k,int num)
{
    while(k<=n)
    {
        tree[k]+=num;
        k+=k&-k;
    }
}

int read(int k) //如果是区间查询就是1~k的和,单点查询就是k点的值
{
    int sum=0;
    while(k)
    {
        sum+=tree[k];
        k-=k&-k;
    }
    return sum;
}

void update(int l,int r,int num) //区间更新
{
    add(l,num);
    add(r+1,-num);
}


int main(void)
{
    int m,i,j;
    scanf("%d",&m);
    n = 0;
    int cnt = 0;
    for(i=0;i<m;i++)
    {
        int t,id;
        scanf("%d%d",&t,&id);
        q[i].t = t;
        q[i].id = id;
        q[i].a = 0;
        if(t == 1)
        {
            v[id].push_back(++n);
            q[i].id = n;
        }

        if(t == 2)
        {
            int a;
            scanf("%d",&a);
            q[i].a = a;
        }
    }
    n = 0;
    dfs(0);
    memset(tree,0,sizeof(tree));
    for(i=0;i<m;i++)
    {
        if(q[i].t == 1)
            update(dfn[q[i].id],dfn[q[i].id],-read(dfn[q[i].id]));
        else if(q[i].t == 2)
            update(dfn[q[i].id],dfn[q[i].id]+sz[q[i].id]-1,q[i].a);
        else
            printf("%d\n",read(dfn[q[i].id]));
    }

    return 0;
}

 

好的,这是一个比较简单的数学题,可以用矩阵快速幂求解。以下是 C++ 代码实现: ```c++ #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long LL; const int MAXN = 2; const int MOD = 1e9 + 7; struct Matrix { LL m[MAXN][MAXN]; Matrix() { memset(m, 0, sizeof(m)); } Matrix operator * (const Matrix& b) const { Matrix c; for (int i = 0; i < MAXN; ++i) { for (int j = 0; j < MAXN; ++j) { for (int k = 0; k < MAXN; ++k) { c.m[i][j] = (c.m[i][j] + m[i][k] * b.m[k][j]) % MOD; } } } return c; } } base, res; Matrix qpow(Matrix a, int b) { Matrix ans; for (int i = 0; i < MAXN; ++i) { ans.m[i][i] = 1; } while (b) { if (b & 1) { ans = ans * a; } a = a * a; b >>= 1; } return ans; } LL gcd(LL a, LL b) { return b == 0 ? a : gcd(b, a % b); } int main() { LL a, b, n; cin >> a >> b >> n; if (n == 1) { cout << a << endl; } else if (n == 2) { cout << b << endl; } else { base.m[0][0] = base.m[0][1] = base.m[1][0] = 1; res = qpow(base, n - 2); LL ans = gcd(a * res.m[0][0] % MOD + b * res.m[1][0] % MOD, b * res.m[1][0] % MOD + b * res.m[1][1] % MOD); cout << ans << endl; } return 0; } ``` 在这段代码中,我们定义了一个 `Matrix` 结构体,它表示一个 $2\times2$ 的矩阵。其中重载了 `*` 运算符,实现了矩阵乘法。 然后,我们定义了一个矩阵快速幂函数 `qpow`,用于求解矩阵的 $n$ 次方。 最后,在 `main` 函数中,我们通过快速幂求出矩阵 $base$ 的 $n-2$ 次方,然后根据题目要求求出 $\gcd(F_N, F_{N+1})$ 并输出即可。 需要注意的是,当 $n=1$ 或 $n=2$ 时,直接输出 $a$ 或 $b$ 即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值