bzoj2159

15 篇文章 0 订阅
11 篇文章 0 订阅

【题意】
给出一棵 n 个点的树,求对于每个点i d(i) 值。

d(i)=ix1xndist(x,i)k

数据范围: 1n50000,1k150

【题解】

这题非常的神……

首先我们发现, xn 能用Stirling数通过一些神奇的方法表示出来……

xn=1<=k<=nS(n,k)×F(x,k)

其中 S(n,k) 为第二类Stirling数, S(n,k)=S(n1,k1)+k×S(n1,k)

F(n,k)=n×(n1)×...×(nk+1)

那么 d(i)=jkS(k,j)×f(i,j)

其中 f(i,j)=xi1xnF(dist(i,x),j)

那么我们就能推出来啦。

但是我们发现,f数组很难通过树形dp求出。考虑到 C(n,k)=F(n,k)k! ,我们更改f数组表示的内容,改为

f(i,j)=xi1xnC(dist(i,x),j)

那么我们就有 f(i,j)=f(i,j)×j! ,我们可以通过pascal定理转移出f’,从而转移出f。

pascal定理: C(n,k)=C(n1,k)+C(n1,k1)

然后我们就可以用树形dp统计啦。

注意对于一个节点,树形dp中的f表示的是其以下的子树的和,还需要统计它的兄弟以及父亲上面的值,需要重新计算。

记录down[x][j]表示 C(dist(i,x),j) 子树对x的贡献
记录up[x][j]表示 C(dist(i,x),j) x的父亲和父亲的父亲…和兄弟的对x的贡献
down[x][j]+=down[son][j]+down[son][j1]
up[x][j]+=up[fa][j]+up[fa[j1]
up[x][j]+=down[fa][j]+down[fa][j1]
up[x][j]=down[x][j]+down[x][j1]
up[x][j]=down[x][j1]+down[x][j2](j2)

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int mod=10007;
const int N=100100;
const int M=200;
int n,k,L,A,B,Q,now;
int tot,ans,h[N];
int s[M][M],c[M][M],fac[M];
int down[N][M],up[N][M];
struct edge{int y,next;}g[N];

void adp(int x,int y){
    g[++tot].y=y;
    g[tot].next=h[x];
    h[x]=tot;
}

void calcup(int x,int fa){
    if (fa){
        up[x][0]=n-down[x][0];
        for (int j=1;j<=k;j++){
            up[x][j]=(up[x][j]+up[fa][j]+up[fa][j-1])%mod;
            up[x][j]=(up[x][j]+down[fa][j]+down[fa][j-1])%mod;
            up[x][j]=(up[x][j]-down[x][j]-down[x][j-1]+2*mod)%mod;
            up[x][j]=(up[x][j]-down[x][j-1]+mod)%mod;
            if (j>=2) up[x][j]=(up[x][j]-down[x][j-2]+mod)%mod;
        }
    }
    for (int i=h[x];i;i=g[i].next)
    if (g[i].y!=fa)
        calcup(g[i].y,x);
}

void calcdown(int x,int fa){
    down[x][0]=1;
    for (int i=h[x];i;i=g[i].next)
    if (g[i].y!=fa){
        calcdown(g[i].y,x);
        down[x][0]+=down[g[i].y][0];
        for (int j=1;j<=k;j++)
            down[x][j]=(down[x][j]+down[g[i].y][j]+down[g[i].y][j-1])%mod;
    }
}

int main(){
    freopen("a.in","r",stdin);
    scanf("%d%d%d%d%d%d%d",&n,&k,&L,&now,&A,&B,&Q);
    for (int i=1;i<n;i++){
        now=(now*A+B)%Q;
        int tmp=i<L?i:L;
        int x=i-now%tmp,y=i+1;
        adp(x,y);adp(y,x);
    }
    s[0][0]=fac[0]=1;
    for (int i=1;i<=k;i++){
        fac[i]=fac[i-1]*i%mod;
        s[i][i]=1;
        for (int j=1;j<i;j++)
            s[i][j]=(s[i-1][j]*j%mod+s[i-1][j-1])%mod;
    }
    calcdown(1,0);
    calcup(1,0);
    for (int i=1;i<=n;i++){
        ans=0;
        for (int j=1;j<=k;j++)
            ans=(ans+s[k][j]*fac[j]%mod*(up[i][j]+down[i][j])%mod)%mod;
        printf("%d\n",ans);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值