【数学】树

题目描述

小 B 也是一名普及组选手,小 B 喜欢图论,尤其喜欢树。
一天,小 B 学习了递归。小 B 想要通过递归生成一棵树,因此他写下了这样的代码:

int cnt=0;
int solve(int n)
{
    if(n==0){cnt++;return cnt-1;}
    int ls=solve(n-1);int rs=solve(n-1);
    adde(ls,rs);return ls;
}

其中 adde(x,y) 表示加入一条连接 x,yx,yx,y 的边。
可以发现,如果运行 solve(n),可以得到一棵有 2n2^n2n 个点的树,这棵树的节点从 000 开始编号。小 B 在 adde 中加入了输出,并将输出重定向到了一个文件,接着调用了 solve(1e6)。显而易见地,这个文件很快变得极大,占据了整个硬盘。接着,小 B 的硬盘坏掉了。

在小 B 的电脑被送去修理的时候,小 B 无法写代码,因此小 B 决定继续思考之前调用 solve(n) 得到的树。小 B 看到了一种算法名为点分治,因此小 B 给出了 qqq 次询问,每次给出 x,dx,dx,d,你需要求出树上有多少个点 yyy 满足 x,yx,yx,y 在树上的最短路长度(经过的边数)等于 ddd。因为这个答案可能很大,你只需要求出这个答案对 998244353998244353998244353 取模的结果。

然而 xxx 也可能非常大,因此小 B 会以以下方式给出 xxx

对于第 iii 次询问,给出 kik_iki 以及 kik_iki两两不同的非负整数 v1,…,vkiv_1,\ldots,v_{k_i}v1,,vki,表示 xxx 的二进制表示中所有为 111 的位为 v1,…,vkiv_1,\ldots,v_{k_i}v1,,vki

n⩽107,q⩽2×105,∑ki⩽106n\leqslant 10^7,q\leqslant 2\times 10^5,\sum k_i\leqslant 10^6n107,q2×105,ki106

题解

首先这题不会是淀粉质(逃

观察这个图非常像树状数组,发现每个点往上爬的时候会减去 lowbit⁡(i)\operatorname{lowbit}(i)lowbit(i),所以至多爬 kik_iki 次,我们需要设计一个 O(∑ki)\mathcal{O}(\sum k_i)O(ki) 的算法。每个点下满足 d=id=id=i 的点是满足杨辉三角的规律的。设当前节点的子树最大深度为 xxx,查询距离为 yyy(深度定义为边的个数),则这个节点下距离 yyy 的个数为 (xy)\dbinom{x}{y}(yx)

我们举两个例子 (x=7,d=3)(x=7,d=3)(x=7,d=3)(x=13,d=4)(x=13,d=4)(x=13,d=4)

x=7x=7x=7 时,我们发现在 777 的所有子树中没有距离他为 333 的点,所以我们往上爬到 666,发现仍然没有。爬到 444 时,由于我往上爬了两层,所以我还需要距离为 111 的点,所以在点 444 中求出距离为 111 的点即为 (21)=2\dbinom{2}{1}=2(12)=2,但是我们发现实际上满足条件的点只有 555,而没有 666,这是因为我们将一条边走了两次,不满足简单路径的性质,所以事实上我们应该减去 (10)=1\dbinom{1}{0}=1(01)=1666 中子树深度为 111,我需要减去深度为 d−2−1d-2-1d21 的点,这个公式是假设我往上爬了 iii 次,则我一定会减掉深度 d−i−1d-i-1di1 的点)。我们不妨继续往下看,爬到 000 时,求出 (40)=1\dbinom{4}{0}=1(04)=1,最后得到总答案为 222

x=13x=13x=13 时,我们不考虑爬到 12,1312,1312,13 的情况,直接考虑 888 的情况,(32)=3\dbinom{3}{2}=3(23)=3,将 11,13,1411,13,1411,13,14 都算了进来,但我们知道 121212 的子树已经不可以再计算了,所以我们减去 (2d−i−1=1)=2\dbinom{2}{d-i-1=1}=2(di1=12)=2 个节点,得到 888 中有一个节点满足条件,以此类推,不再详述了。

所以经过我们的探索,我们会发现总答案满足(注意观察以下 iii 跟上面的变化,跟数组有关的,第 iiiviv_ivi 只爬了 i−1i-1i1 次):
∑i=1k(vid−i+1)−(vi−1d−i)×[i≠1] \sum\limits_{i=1}^k \dbinom{v_i}{d-i+1}-\dbinom{v_{i-1}}{d-i}\times [i\neq 1] i=1k(di+1vi)(divi1)×[i=1]

上面的公式其实不完全正确,我们需要在 vvv 数组的最后添加一个 111,表示第 nnn 位也有,这样才能计算得到从 000 出发的情况,题目没保证 vvv 单调递增,记得排序

考虑这个算法目前的瓶颈在哪,在于处理组合数中阶乘的逆元,最大可以达到 n!n!n! 的逆元,所以我们需要学习 O(n)\mathcal{O}(n)O(n) 的阶乘逆元求法(不会只有我不会吧/kk):
inv⁡(i!)=inv⁡[(i+1)!]×(i+1) \operatorname{inv}(i!)=\operatorname{inv}[(i+1)!]\times (i+1) inv(i!)=inv[(i+1)!]×(i+1)

这样即可做到 O(max⁡(n,∑k))\mathcal{O}(\max(n,\sum k))O(max(n,k)) 求值。

#include<bits/stdc++.h>
#define int long long
#define mid ((l+r)>>1)
#define fir first
#define sec second
#define lowbit(i) (i&(-i))
using namespace std;
const int N=1e7+5;
inline int read(){
    char op=getchar();
    int w=0,s=1;
    while(op<'0'||op>'9'){
        if(op=='-') s=-1;
        op=getchar();
    }
    while(op>='0'&&op<='9'){
        w=(w<<1)+(w<<3)+op-'0';
        op=getchar();
    }
    return w*s;
}
const int mod=998244353;
int Mul(int a,int b){return (a%mod*b%mod)%mod;}
int Add(int a,int b){return (a+b)%mod;}
int Dec(int a,int b){return (a-b+mod)%mod;}
int Pow(int a,int k){
    int ans=1;
    while(k){
        if(k&1) ans=Mul(ans,a);
        a=Mul(a,a);
        k>>=1;
    }
    return ans;
}
int inv(int x){return Pow(x,mod-2);}
int jc[N],invjc[N],x[N];
int C(int n,int m){
    if(n>m||n<0||m<0) return 0;
    return Mul(jc[m],Mul(invjc[n],invjc[m-n]));
}
signed main(){
    int n=read(),q=read();
    jc[0]=1;
    invjc[0]=1;
    for(register int i=1;i<=n;i++) jc[i]=Mul(jc[i-1],i);
    invjc[n]=Pow(jc[n],mod-2);
    for(register int i=n-1;i>=1;i--) invjc[i]=Mul(invjc[i+1],(i+1));
    while(q--){
        int k=read(),ans=0;
        for(register int i=1;i<=k;i++) x[i]=read();
        x[k+1]=n,k++;
        int d=read();
        sort(x+1,x+k+1);
        for(register int i=1;i<=k;i++){
            ans=Add(ans,C(d-i+1,x[i]));
            if(i!=1) ans=Dec(ans,C(d-i,x[i-1]));
        }
        printf("%lld\n",ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值