BUAAOJ 610 北航校赛 前前前世 dp 预处理

来源:互联网 发布:宁波行知小学 编辑:程序博客网 时间:2024/05/01 08:08
题目地址:https://biancheng.love/problem/610/index

题意:给出一棵无穷结点的二叉树,根节点为 1 ,并且 对于结点 i ,它的两个子结点分别是 2i 和 2i + 1 在结点 p 的前 n 层子树中寻找两个结点 x 和 y ,满足 y 是 x 的子结点的子结点的子结点,且 x ≡ y ≡ 1 (mod k) 问可能的二元组 (x, y) 的数目模 1e9 + 7 的值 。数据范围:1 ≤ T < 1000, 2 ≤ n < 50000, 1 < k < 1018 , 1 ≤ p < 1018

思路和代码都写的不是很好,以后有空再重新写一下
思路:(先说题解思路吧,然后说下自己的)
首先,当k>=15时,此题是无解的,证明:由祖先关系可知 y = 8x + d, d ∈ [0, 2^3 ),由同余关系可知 1 ≡ y ≡ 8x + d ≡ 8 + d (mod k), 当 k ≥ 15 时, 8 ≤ 8 + d ≤ 15 ,无解。
然后,题目要求的节点对很特殊,根节点一定是1,其对应的节点数一定是该节点的第三层子树中1的个数,记为cnt。递推关系是f [k] [i] [j] = 1, f [k] [i] [j] = 0 (0 ≤ j < k, j != 1),f[k] [i] [j] = f[k] [i - 1] [j] + f[k] [i - 1] [(j + 2 ^ (i - 1)) % k]
所以对于一个询问,求出所给根节点对应子树的[1, n - 3]层中1的个数乘cnt就好了(根节点为第1层)。防止重复计算,预处理。
题解给出了一个函数 f [k] [i] [j] ,表示长度为 2^i 且最小数字模 k 意义下为 j 的整数区间里模 k 意义下为 1 的数字个数。对于每次询问给出的根节点,都可以用这个函数进行O( n )的求和。(详见第一份代码)。这个还可以加一个前缀和,回答可以达到O( 1 )

自己当初看题解的时候觉得对 f 函数的描述真是拗口……然后自己想了一个函数 g[k] [i] [j]表示模 k 意义下,根节点为1的树第 i 层节点中模 k 结果为 j 的个数。递推关系是:g [k-2] [i+1] [(j*2)%k] += g [k-2] [i] [j] ,g [k-2] [i+1] [(j*2+1)%k] += g [k-2] [i] [j]。
根节点为 1 的子树,只要算好自己最深一层对答案的贡献,然后加上前面已算好的其他深度对答案的贡献就好了。
根节点不是 1 的子树,因为g函数是关于根节点为 1 的子树的函数,所以它的答案是自己两个子树的答案。
详见第二份代码。

很抱歉,因为做这题的时候数组开的不够大……导致疯狂wa,现在自己写的这篇博客有点懒了。

代码:

#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;typedef long long LL;const int MAXN=50000+5;const int MOD=1e9+7;LL a[20][MAXN][20];LL solve(int k,int n,int p){    LL ret=0;    LL cnt=0;    for(int i=8;i<16;++i) if(i%k==1) ++cnt;    n-=3;    for(int i=0;i<=n;++i){        (ret+=cnt*a[k][i][p])%=MOD;、        (p<<=1)%=k;    }    return ret;}int main(){    for(int k=2;k<15;++k){        LL ret=1;        for(int i=0;i<k;++i) a[k][0][i]=0;        a[k][0][1]=1;        for(int i=1;i<MAXN;++i){            for(int j=0;j<k;++j){                (a[k][i][j]+=a[k][i-1][j])%=MOD;                (a[k][i][j]+=a[k][i-1][(j+ret)%k])%=MOD;            }            (ret<<=1)%=k;        }    }    int T;    scanf("%d",&T);    LL k,n,p;    while(T--){        scanf("%lld%lld%lld",&k,&n,&p);        if(k>=15){            puts("0");            continue;        }        --n;        p%=k;        printf("%lld\n",solve(k,n,p));    }}


#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>#include <queue>using namespace std;#define MP make_pairtypedef long long LL;typedef pair<int,int> P;const int MAXN=5e4+5;const int MOD=1e9+7;int limk=8,limi=8;int a[13][MAXN][14];LL ans[13][MAXN][14];int main(){    for(int k=2;k<15;++k){        a[k-2][0][1]=1;        for(int i=0;i<MAXN-1;++i){            for(int j=0;j<k;++j){                (a[k-2][i+1][(j*2)%k]+=a[k-2][i][j])%=MOD;                (a[k-2][i+1][(j*2+1)%k]+=a[k-2][i][j])%=MOD;            }        }        ans[k-2][3][1]=1LL*a[k-2][0][1]*a[k-2][3][1]%MOD;        for(int i=4;i<MAXN;++i) ans[k-2][i][1]=(ans[k-2][i-1][1]+1LL*a[k-2][i-3][1]*a[k-2][3][1])%MOD;        for(int i=1;i<MAXN;++i){            for(int j=0;j<k;++j) if(j!=1){                (ans[k-2][i][j]+=ans[k-2][i-1][(j*2)%k]+ans[k-2][i-1][(j*2+1)%k])%=MOD;            }        }    }    int T;    scanf("%d",&T);    LL k,n,p;    while(T--){        scanf("%lld%lld%lld",&k,&n,&p);        if(k>=15){            puts("0");            continue;        }        --n;        printf("%lld\n",ans[k-2][n][p%k]);    }}


0 0
原创粉丝点击