hihocoder#1471 : 拥堵的城市

#1471 : 拥堵的城市

时间限制:18000ms

单点时限:1000ms

内存限制:256MB

描述

你的面前有一个城市,这个城市是一个由 n 个节点构成的树。现在你想要在一些城市之间开一些公交车路线来方便城市的居民。

一个公交车路线是一个连接城市中两个点的(无向)路径。你选择的公交车路线的集合需要满足一下的性质。

1. 每个路线的长度(经过边的数量)至少为1,也就是说两个端点不能重合。

2. 对于任何一个节点,最多作为一个路线的端点。

3. 任意一条边最多被k个路线经过。

你想要知道一共有多少选择公交车路线的集合的方案,两个方案不同当且仅当一个集合中存在某条路线但另一个集合中不存在。注意一条路线也不开也算一种方案。

你只需要输出对109+7取模后的结果。

输入

树的点从1开始标号。

第一行两个数 n 和 k 分别表示树的点数和每条边被经过的最多的次数。

接下来 n-1行每行两个数a和b表示一条边。

n, k ≤ 300

输出

一行一个数表示答案。

样例输入

8 5
1 2
1 3
3 4
3 5
1 6
4 7
6 8

样例输出

764

EmacsNormalVim

 GCC G++ C# Java Python2 

 

题解:

orz。。。

这个题目属于比较直接的树形dp的问题。

用dp[i][j]表示当前考虑到子树i,往上有j条路径连出去。 
在转移的时候计算一下如何将孩子们连上来的路径凑成对,或者将连上的路径继续往上连,或者将连上来的路径在该点结束 
这几类情况就可以了。

代码:

​
#include <stdio.h>
#include <stdlib.h>
#define p 1000000007
using namespace std;

int n,m,i,j,k,u,v;
int son[305],Next[605],ed[605],size[305],tot;
int f[305][305],ft[305],C[305][305],fac[305];  //f[i][j]表示j条边经过i且通向i的父亲
bool vis[305];

void add(int &a,int b){a+=b;if(a>=p)a-=p;}
void dfs(int x)
{
	int i,j,k,l;
	vis[x]=true;
	size[x]=1;
	f[x][0]=f[x][1]=1;
	for(i=son[x];i;i=Next[i])
	if(!vis[ed[i]])
	{
		dfs(ed[i]);
		for(j=0;j<=size[x]+size[ed[i]];++j)ft[j]=0;
		for(j=0;j<=size[x];++j)
		for(k=0;k<=size[ed[i]];++k)
		for(l=0;l<=j&&l<=k;++l)//枚举l条边经过i且通向i的其他儿子,意思就是不往它的父亲去
		add(ft[j+k-l-l],(long long)f[x][j]*f[ed[i]][k]%p*C[j][l]%p*C[k][l]%p*fac[l]%p); //直接搞一下
		size[x]+=size[ed[i]];
		for(j=0;j<=size[x];++j)f[x][j]=ft[j];
	}
	for(j=m+1;j<=n;++j)f[x][j]=0;
}

int main()
{
	scanf("%d%d",&n,&m);
	C[0][0]=fac[0]=1;
	for(i=1;i<=n;++i)fac[i]=(long long)fac[i-1]*i%p;
	for(i=0;i<=n;++i)
	for(j=0;j<=i;++j)
	{
		if(C[i][j]>=p)C[i][j]-=p;
		C[i+1][j]+=C[i][j];
		C[i+1][j+1]+=C[i][j];
	}
	for(i=1;i<n;++i)
	{
		scanf("%d%d",&u,&v);
		++tot;Next[tot]=son[u];son[u]=tot;ed[tot]=v;
		++tot;Next[tot]=son[v];son[v]=tot;ed[tot]=u;
	}
	dfs(1);
	printf("%d\n",f[1][0]);
}

​

猜你喜欢

转载自blog.csdn.net/qq_41510496/article/details/81169788