【题目描述】
Samjia和Peter不同,他喜欢玩树。所以Peter送给他一颗大小为n的树,节点编号从1到n。
Samjia要给树上的每一个节点赋一个[1,m]之间的权值,并使得有边直接相连的两个节点的权值之差的绝对值 ≥ k。请你告诉Samjia有多少种不同的赋值方案,只用求出答案对10^9+7(1000000007)取模得到的结果。【Sample Input】
(输入数据的第一行包含一个整数 T,测试数据组数。
每组数据的第一行包含三个整数 n、m 和 k。
接下来 n − 1 行,每行包含两个整数 u 和 v, 代表节点 u 和 v 之间有一条树边。)
3
2 2 0
1 2
3 3 2
1 3
1 2
3 3 1
1 2
2 3【Sample Output】
(对于每组数据输出一个整数,代表所求的答案。)
4
2
12【数据范围】
测试点编号 m≤ 特殊约定
1,2 100 无
3,4 10000 无
5,6 10^9 第2-n号节点与1号节点直接相连
7,8 10^9 第i号节点与第i+1号节点直接相连
9,10 10^9 无
【题解】题目看完知道是树形dp
{30%} 裸的随便写,暴力方程:f[i][j]=∏(sigma(f[son[i]][l]))
f[i][j] 表示节点 i 的数值为 j 时的方案数,abs(l-j)<=k;
{60%} 在上式的基础上,对于每个儿子维护前缀和即可;
{100%} 如果我们仔细观察,可以发现一个性质:
对于每个节点x,对于f[x]的中间某一段值可能会出现重复,而重复值段旁的前后两段是对称的。
证明的话,简单的来看,对于叶子节点的每个值答案都是1。那么其父亲累加累乘的时候,到中间就会出现一段相等的值,因为这些值所覆盖的儿子的值域对应的方案数是相等的,而前后两段累加累乘计算的值相同 //貌似怎么解释都有点别扭。。。
所以这些相同的点我们只需要记录其中一个。接下来考虑,不同的点最多有多少个?
从叶子节点往上推,每层最多比上层多 k 个,所以对于任意节点,最多有 (n-1)*k 个不同的值。
//蒟蒻→QAQ个人到了这里其实还是不会打。。。
☆思路和写法整理一下☆
(1) 因为对于每个点有很多相同的,要记录的就是取值范围在:max((n-1)*k,m) 的方案。
(2) 计算每个节点时,先求出当前点 x 取值为1时儿子可取值的和sum{就是一个后缀},随着 x 取值的增大修改sum并计算出当前取值的方案数。具体还是看程序。
#include <cstdio>
#include <iostream>
#include <cstring>
#define LL long long
#define mo 1000000007
struct edge{ int to,nxt;}e[205];
int T,n,m,k,l,cnt,f[105][100005],fi[105];
void add(int u,int v)
{
e[++cnt].to=v;e[cnt].nxt=fi[u];fi[u]=cnt;
}
LL qsm(int x,int y)
{
if (y==1) return x;
LL t=qsm(x,y>>1);
if (y&1) return t*t%mo*x%mo;
else return t*t%mo;
}
LL sum(int x,int y)
{
LL s=0;
for (int i=y;i<=l;++i) (s+=f[x][i])%=mo;
for (int i=m;i>m-l && i>l && i>=y;--i) (s+=f[x][m-i+1])%=mo;
int u,v;
u=std::max(l+1,y);v=m-l;
if (u<=v) (s+=(1ll*f[x][l]*(v-u+1))%mo)%=mo;
return s;
}
void dfs(int x,int fa)
{
for (int i=1;i<=l;++i) f[x][i]=1;
for (int i=fi[x];i;i=e[i].nxt)
if (e[i].to!=fa)
{
dfs(e[i].to,x);
int p=sum(e[i].to,k+1);
for (int j=1;j<=l;++j)
{
if (j>k) (p+=f[e[i].to][j-k])%=mo;
(p+=mo)%=mo;
f[x][j]=1ll*p*f[x][j]%mo;
if (j+k<=m)
{
if (j+k<=l) (p-=f[e[i].to][j+k])%=mo;
else if (j+k>m-l) (p-=f[e[i].to][m-j-k+1])%=mo;
else (p-=f[e[i].to][l])%=mo;
}
}
}
}
int main()
{
for (scanf("%d\n",&T);T;--T)
{
scanf("%d%d%d\n",&n,&m,&k);
memset(fi,0,sizeof(fi));cnt=0;
for (int i=1;i<n;++i)
{
int u,v;scanf("%d%d\n",&u,&v);
add(u,v);add(v,u);
}
if (!k) printf("%lld\n",qsm(m,n));
else
{
l=std::min((n-1)*k,m);
dfs(1,0);
printf("%lld\n",sum(1,1));
}
}
return 0;
}
【题外话】每次打题都会有一些小毛病,有aufeas战god在真是太好了 QAQ QwQ