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

题目地址: 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 
#include 
#include 
#include 
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=15){
            puts("0");
            continue;
        }
        --n;
        p%=k;
        printf("%lld\n",solve(k,n,p));
    }
}


#include 
#include 
#include 
#include 
#include 
using namespace std;
#define MP make_pair
typedef long long LL;
typedef pair 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=15){
            puts("0");
            continue;
        }
        --n;
        printf("%lld\n",ans[k-2][n][p%k]);
    }
}


你可能感兴趣的:(dp,树)