【JZOJ4921】【NOIP2017提高组模拟12.10】幻魔皇

题目描述

幻魔皇拉比艾尔很喜欢斐波那契树,他想找到神奇的节点对。
所谓斐波那契树,根是一个白色节点,每个白色节点都有一个黑色节点儿子,而每个黑色节点则有一个白色和一个黑色节点儿子。神奇的节点对则是指白色节点对。
请问对于深度为n的斐波那契树,其中距离为i的神奇节点对有多少个?拉比艾尔需要你对于1<=i<=2n的所有i都求出答案。

数据范围

对于100%的数据n<=5000。

=w=

性质:
以任意一个白点为根作子树时,在这棵子树中,白点和黑点的数量随深度呈斐波那契数列形态。
设当深度为i时,白点数量为w[i],白点数量前缀和为sum[i],黑点数量前缀和为f[i]。这些可以O(n)预处理。


我们对贡献分门别类:

1.lca是白点类

显然当两个点的lca是白点时,其中一个点是这个白点。
我们以每一个白点为根做子树来计算答案,具体地:

我们枚举一个i,表示这两个点的距离。
所有深度在[1,ni]的白点都拥有与它距离为i的儿子,所以都可以对ans[i]作贡献。
然后这些白点的贡献都是相同的,而且都是w[i+1]

那么对于一个i,ans[i]+=sum[ni]w[i+1]

2.lca是黑点类

同样枚举一个i和一个j,其中一个结点与lca距离为i,另一个结点与lca距离为j。
所有深度在[1,nmax(i,j)]的黑点都可以贡献ans[i+j]

所以对于i,j,ans[i+j]+=f[nmax(i,j)]w[i]w[j+1]

代码

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#define ll long long
#define ln(x,y) (ll(log(x)/log(y)))
using namespace std;
const char* fin="raviel.in";
const char* fout="raviel.out";
const ll inf=0x7fffffff;
const ll mo=123456789;
const ll maxn=10007;
ll n,i,j,k;
ll f[maxn],g[maxn],sum[maxn],ans[maxn];
int main(){
    freopen(fin,"r",stdin);
    freopen(fout,"w",stdout);
    scanf("%d",&n);
    f[1]=0;
    f[2]=1;
    for (i=3;i<maxn;i++) f[i]=(f[i-2]+f[i-1])%mo;
    for (i=1;i<maxn;i++) f[i]=(f[i]+f[i-1])%mo;
    g[1]=1;
    g[2]=0;
    for (i=3;i<maxn;i++) g[i]=(g[i-2]+g[i-1])%mo;
    for (i=1;i<maxn;i++) sum[i]=(sum[i-1]+g[i])%mo;
    for (i=1;i<n;i++){
        k=n-i;
        ans[i]=(ans[i]+sum[k]*g[i+1])%mo;
    }
    for (i=1;i<n;i++)
        for (j=1;j<n;j++){
            k=n-max(i,j);
            ans[i+j]=(ans[i+j]+f[k]*g[i]%mo*g[j+1])%mo;
        }
    for (i=1;i<=n*2;i++) printf("%lld ",ans[i]);
    printf("
");
    return 0;
}

=o=

我认为在做这道题的时候的瓶颈是正确地对贡献分门别类。
然后要先枚举距离,在根据这个距离计数。
由于问题是每种距离的个数,那么这个问题决定了这道题必须先枚举距离来计数的特点。
至今还在纠结正确的枚举方式是怎样。
还有待决定。

原文地址:https://www.cnblogs.com/hiweibolu/p/6714819.html