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

7 篇文章 0 订阅

题意:给出一棵无穷结点的二叉树,根节点为 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_pair
typedef 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值