【NOIP2016提高A组五校联考4】label

题目

这里写图片描述

题目

20%算法

fi,j 表示第i个节点选了j这个权值的方案数。
显然转移方程为,

fi,j=Πv=son(i)(k=1jkfv,k+k=j+kmfv,k)

40%算法

接着上面的想法,
观察转移方程,发现,求和部分其实是两段连续的,那么将 fi 求一个前缀和。

100%算法

观察 f 数组,发现其实f是对称的,而且中间的一段是相同的,设深度为x,那么前面就有 (x1)k 个不同,然后中间有一段相同,后面又有 (x1)k 个不同。
那么就可以只求出前 (x1)k+1 个,前缀和就可以直接算出来(程序中我直接求出前10010个的值,没有计算x)。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <queue>
const long long maxlongint=2147483647;
const long long mo=1000000007;
const long long N=105;
using namespace std;
long long f[N][N*N*2],_,n,m,k,next[N*2],last[N*2],to[N*2],tot,same[N],sum1[N][N*N],sum2[N][N*N];
long long pos[N];
long long max(long long x,long long y)
{
    if(x<y) return y;else return x;
}
long long min(long long x,long long y)
{
    if(x>y) return y;else return x;
}
long long bj(long long x,long long y)
{
    next[++tot]=last[x];
    last[x]=tot;
    to[tot]=y;
}
long long sum(int x,int y)
{
    if(y<=10010) return sum1[x][y];
    else
    if(y<m-pos[x]+1)
    {
        return (sum1[x][pos[x]]+same[x]*(y-pos[x])%mo)%mo;
    }
    else
    {
        return (sum1[x][pos[x]]+(m-pos[x]-pos[x])*same[x]%mo+sum2[x][(y-(m-pos[x]))])%mo;
    }
}
long long dg(long long x,long long fa)
{
    bool q=false;
    for(long long i=last[x];i;i=next[i])
    {
        long long j=to[i];
        if(j!=fa)
        {
            dg(j,x);
            q=true;
        }
    }
    for(long long i=1;i<=min(10010,m);i++) f[x][i]=1;
    for(long long i=last[x];i;i=next[i])
    {
        long long j=to[i];
        if(j!=fa)
        {
            for(long long l=1;l<=min(10010,m);l++)
            {
                long long t=0;
                t=(t+(sum(j,max(0,l-k))-sum(j,0)+mo))%mo;
                if(l+k<=m) 
                {
                    t=(t+(sum(j,m)-sum(j,l+k-1)+mo))%mo;
                    if(max(0,l-k)==l+k)
                        t=(t-f[j][l+k]+mo)%mo;
                }
                f[x][l]=(f[x][l]*t)%mo;
            }
        }
    }
    if(10010<m)
    {
        for(int i=1;i<=10010;i++)
        {
            if(f[x][i]==f[x][i+1])
            {
                same[x]=f[x][i];
                pos[x]=i-1;
                break;
            }
        }   
    }
    else
    {
        same[x]=f[x][m/2];
    }
    for(int i=1;i<=min(10010,m);i++)
        sum1[x][i]=(sum1[x][i-1]+f[x][i])%mo;
    for(int i=1;i<=min(10010,m);i++)
        sum2[x][i]=(sum2[x][i-1]+f[x][pos[x]-i+1])%mo;
}
int main()
{
    scanf("%lld",&_);
    while(_--)
    {
        scanf("%lld%lld%lld",&n,&m,&k);
        memset(f,0,sizeof(f));
        memset(last,0,sizeof(last));
        memset(next,0,sizeof(next));
        memset(sum1,0,sizeof(sum1));
        memset(sum2,0,sizeof(sum2));
        memset(pos,0,sizeof(pos));
        tot=0;
        for(long long i=1;i<=n-1;i++)
        {
            long long x,y;
            scanf("%lld%lld",&x,&y);
            bj(x,y);
            bj(y,x);
        }
        dg(1,0);
        printf("%lld\n",sum(1,m));
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值