BZOJ2159:Crash 的文明世界 (第二类stirling数+组合数学+树形DP)

23 篇文章 0 订阅

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2159


题目分析:先说一下部分分怎么拿。

20% 20 % :直接以每个点为根DFS一遍,求出其它点的深度。预处理 1k 1 k ~ nk n k 统计答案。时间复杂度 O(n2+nk) O ( n 2 + n k )

50% 50 % :令 S[node][d]=unodedis(node,u)d S [ n o d e ] [ d ] = ∑ u ∈ n o d e d i s ( n o d e , u ) d 。当计算node父亲节点fa的S值时,显然有:

S[fa][d]=p=0dCpdS[node][p] S [ f a ] [ d ] = ∑ p = 0 d C d p ∗ S [ n o d e ] [ p ]

第一遍DFS完之后,再用一次DFS计算node的祖先对node的贡献。这个套路很常见,就不再多说了。时间复杂度是 O(nk2) O ( n k 2 )

100% 100 % 发现计算S[fa][d]是个卷积的形式,所以用NTT优化

上面的方法虽然时间是 O(nklog(k)) O ( n k log ⁡ ( k ) ) 的,但绝对会T。

从最初的式子考虑,我们发现 dis(i,j)k d i s ( i , j ) k 是个幂,于是考虑用第二类stirlng数将其代换:

dk=p=0dCpdS(k,p)(p!) d k = ∑ p = 0 d C d p ∗ S ( k , p ) ∗ ( p ! )

其中 S(k,p) S ( k , p ) 是第二类stirling数。由于当 p>k p > k S(k,p) S ( k , p ) 值为0,d的上界还可以跟k取个min。 S(k,p) S ( k , p ) 可以用递推式 S(i,j)=S(i1,j1)+S(i1,j)j S ( i , j ) = S ( i − 1 , j − 1 ) + S ( i − 1 , j ) ∗ j 求,也可以用容斥+NTT算一行。不过因为k很小,所以直接递推即可。由于k是定值,为了方便直接记 h[p]=S(k,p)(p!) h [ p ] = S ( k , p ) ∗ ( p ! )

现在我们主要关注 Cpd C d p 这一项。不妨令 f[node][p] f [ n o d e ] [ p ] 表示 unodeCpdis(node,u) ∑ u ∈ n o d e C d i s ( n o d e , u ) p 。考虑计算node的父亲fa的f值,发现node子树中的所有点距离+1。根据杨辉三角 Cpd+1=Cpd+Cp1d C d + 1 p = C d p + C d p − 1 ,可以算出 f[node][p1]+f[node][p] f [ n o d e ] [ p − 1 ] + f [ n o d e ] [ p ] f[fa][p] f [ f a ] [ p ] 有贡献。同样用两次DFS即可求出所有答案。

一开始我想这题的时候忘了杨辉三角的公式,然后发现从 Cpd C d p 转移到 Cpd+1 C d + 1 p 很困难,以为要对不同的d分开处理,然后就走远了。以后要多做些组合数学的题才行QAQ。


CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=50100;
const int maxk=160;
const int M=10007;

struct edge
{
    int obj;
    edge *Next;
} e[maxn<<1];
edge *head[maxn];
int cur=-1;

int f[maxn][maxk];
int g[maxn][maxk];

int S[maxk][maxk];
int h[maxk];
int fac[maxk];

int fa[maxn];
int n,k;

void Add(int x,int y)
{
    cur++;
    e[cur].obj=y;
    e[cur].Next=head[x];
    head[x]=e+cur;
}

void Dfs1(int node)
{
    f[node][0]=1;
    for (edge *p=head[node]; p; p=p->Next)
    {
        int son=p->obj;
        if (son==fa[node]) continue;
        fa[son]=node;
        Dfs1(son);

        f[node][0]=(f[node][0]+f[son][0])%M;
        for (int i=1; i<=k; i++)
            f[node][i]=(f[node][i]+f[son][i]+f[son][i-1])%M;
    }
}

void Dfs2(int node)
{
    for (edge *p=head[node]; p; p=p->Next)
    {
        int son=p->obj;
        if (son==fa[node]) continue;

        g[son][0]=f[node][0]-f[son][0];
        for (int i=0; i<=k; i++)
            g[son][i]=(f[node][i]-f[son][i]-f[son][i-1]+2*M)%M;
        for (int i=0; i<=k; i++) g[son][i]=(g[son][i]+g[node][i])%M;
        for (int i=k; i>=1; i--) g[son][i]=(g[son][i]+g[son][i-1])%M;

        Dfs2(son);
    }
}

int main()
{
    freopen("2159.in","r",stdin);
    freopen("2159.out","w",stdout);

    int L,now,A,B,Q,tmp;
    scanf("%d%d%d",&n,&k,&L);
    for (int i=1; i<=n; i++) head[i]=NULL;
    scanf("%d%d%d%d",&now,&A,&B,&Q);
    for (int i=1; i<n; i++)
    {
        now=(now*A+B)%Q;
        tmp=((i<L)?i:L);
        int x=i-now%tmp,y=i+1;
        Add(x,y);
        Add(y,x);
    }

    for (int i=1; i<=k; i++) S[i][1]=1;
    for (int i=2; i<=k; i++)
        for (int j=2; j<=i; j++)
            S[i][j]=(S[i-1][j-1]+S[i-1][j]*j)%M;
    fac[0]=1;
    for (int i=1; i<=k; i++) fac[i]=fac[i-1]*i%M;
    for (int i=1; i<=k; i++) h[i]=S[k][i]*fac[i]%M;

    Dfs1(1);
    Dfs2(1);

    for (int i=1; i<=n; i++)
    {
        int ans=0;
        for (int j=0; j<=k; j++) ans=(ans+ (f[i][j]+g[i][j])*h[j] )%M;
        printf("%d\n",ans);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值