【泉州一中国庆集训day5】label

题目链接http://v.qzyz.com/contest/292/problem/2
题目大意:Samjia和Peter不同,他喜欢玩树。所以Peter送给他一颗大小为n的树,节点编号从1到n。Samjia要给树上的每一个节点赋一个[1,m]之间的权值,并使得有边直接相连的两个节点的权值之差的绝对值 ≥ k。请你告诉Samjia有多少种不同的赋值方案,只用求出答案对10^9+7(1000000007)取模得到的结果。有多组数据。
数据范围:m<=10^9 , n<=100 , k<=100

题解:当m较小时,很容易想到树形dp,设f[ i ][ j ]表示点 i 权值为 j 时的方案数,得到方程f[ i ][ j ]=∏(∑f[son][x]),其中son为点 i 的子节点,| x - j |>=k。
但是当m达到10^9时,就不能直接这样做了。
观察 f 的值,我们发现,对于叶节点,f[i][j]均为1;对于叶节点的父亲结点,f的值在[k,m-k]范围内相同,而在两边对称。继续观察可以发现,每一个节点的 f 值总是由两边向内对称分布,并且中间有一段连续的相同的值,而两边的不同的值的个数与它到距离最远的后代叶子节点有关,具体来说,假设这个距离是x,那么这个个数不超过x*k。
于是我们得到了一个优化的方法, j 的范围只需要记到min(m,(n-1)*k)就可以了,此外f数组中的任何一个位置的数都可以由此得到。详见程序。
时间复杂度O(n^2*k)

代码如下:

#include <algorithm>
#include <cstring>
#include <cstdio>
const int mo=1000000007;
int to[205],ne[205],fi[105],f[105][10005],u[105],
    i,n,m,k,l,x,y,ans,tot=0,T;
void add(int x,int y)
{
    to[++tot]=y;ne[tot]=fi[x];fi[x]=tot;
}
int calc(int x,int y)//计算f[x][y]+f[x][y+1]+...+f[x][m] 
{
    int sum=0;
    for (int i=y;i<=l;++i) sum=(sum+f[x][i])%mo;//小于等于l的直接加 
    for (int i=m;i>=m-l+1;--i)
        if (i<=l || i<y) break; 
            else sum=(sum+f[x][m-i+1])%mo;//i>=m-l+1时对称到1~l 
    int L=std::max(y,l+1),R=m-l;
    if (R>=L) sum=(sum+1ll*(R-L+1)*f[x][l])%mo;//中间相同部分*个数 
    return sum;
}
void dfs(int x)
{
    u[x]=1;
    for (int i=1;i<=l;++i) f[x][i]=1;
    for (int i=fi[x],j,y;i;i=ne[i])
    if (!u[to[i]])
    {
        dfs(to[i]);
        y=calc(to[i],k+1);//刚开始的f[x][1]=f[to[i]][k+1...m]
        for (j=1;j<=l;++j)
        {
            if (j>k) y+=f[to[i]][j-k];//j往后移,减去f[to[i]][j-k]
            y=(y%mo+mo)%mo;
            f[x][j]=1ll*f[x][j]*y%mo;
            if (j+k<=m)//加上f[to[i]][j+k]
            {
                if (j+k<=l) y-=f[to[i]][j+k];
                    else if (j+k>m-l) y-=f[to[i]][m-j-k+1];
                        else y-=f[to[i]][l];
            }
        }
    }
}
int qsm(int x,int y)
{
    int i=1;x%=mo;
    for (;y;y>>=1,x=1ll*x*x%mo)
        if (y&1) i=1ll*i*x%mo;
    return i;
}
int main()
{
    for (scanf("%d\n",&T);T--;tot=0)
    {
        memset(u,0,sizeof u);
        memset(fi,0,sizeof fi);
        scanf("%d%d%d\n",&n,&m,&k);
        for (i=1;i<n;++i)
        {
            scanf("%d%d\n",&x,&y);
            add(x,y);add(y,x);
        }
        l=std::min(m,(n-1)*k);//需要记的范围 
        if (k<0) ans=0;
        else if (!k) ans=qsm(m,n);
        else
        {
            dfs(1);
            ans=calc(1,1);
        }
        printf("%d\n",ans);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值