牛客 每日一题 10 树 题解(dp or 组合数学)

42 篇文章 0 订阅

题目链接

题目大意

需要你将一棵树分成若干个连通块,连通块上的颜色相同,不同连通块之间的颜色不同。

题目思路

前言

emmm,感觉题目理解还是比较容易,自己没做过类似的题,看完题解应该也不算太难吧。其实只要前面两个输入就行了

正文
方法一

时间复杂度O(nk)
在树中选择任意一个叶子结点作为起点,沿着边一个点接一个点的涂色。定义dp[i][j]为前i个点使用了j种颜色的方案数,状态转移方程:
若第i个点选用未使用的颜色,则dp[i][j]+=dp[i-1][j-1]*(k-j+1)
若第i个点选用已经使用过的颜色,则必须和i−1的颜色相同,若和更前面的结点颜色相同则在路径中会经过i−1不符合题意,则dp[i][j]+=dp[i-1][j]

方法二

复杂度 O(nlogn)
但是仔细一想,把一棵树分成两个连通块不就是删除一条边,那分成 x 个连通块不就是选择 x-1 条边删除,然后在颜色里面选 x 种颜色给涂上去。这就是一个简单的组合数学问题。

如果把树分成 x 个连通块,那么就要选择 x-1 条边,也就是有 C(n-1,x-1)种方案,对于每一种分连通块的方案,可以有 C(k,x)种取颜色的方案,最后把颜色涂上去有 x 的全排列种也就是 x!种方案,那么答案 ans= ∑ x = 1 m i n ( n , k ) C ( n − 1 , x − 1 ) ∗ C ( k , x ) ∗ x ! \sum_{x=1}^{min(n,k)} C(n-1,x-1)*C(k,x)*x! x=1min(n,k)C(n1,x1)C(k,x)x!
其实这道题可以数据范围可以出到 10e6 的。

代码1

#include<cstdio>
using namespace std;
typedef long long ll;
const int maxn=3e2+5;
const int mod=1e9+7;
int n,k;
ll dp[maxn][maxn],ans;
int main(){
	scanf("%d %d",&n,&k);//多余的输入在牛客上面可以不输 
	dp[0][0]=1;//初始化 
	for(int i=1;i<=n;i++){
		for(int j=1;j<=k;j++){
			dp[i][j]=(dp[i-1][j]+dp[i-1][j-1]*(k-j+1))%mod;
		}
	}
	for(int i=1;i<=k;i++){
		ans=(ans+dp[n][i])%mod;
	} 
	printf("%lld\n",ans);
	return 0;
} 

代码2

注意 如果三个数相乘都要取mod则要这么写
ans=((a%mod)(b%mod)%mod)(c%mod)))%mod//前面两个还要多取一次mod

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=3e2+5;
const int mod=1e9+7;
int n,k;
ll fac[maxn],ans;
ll qpow(ll a,ll b){
	ll ans=1,base=a;
	while(b>0){
		if(b&1){
			ans=((ans%mod)*(base%mod))%mod;
		}
		base=((base%mod)*(base%mod))%mod;
		b=b>>1;
	}
	return ans;
}
ll c(ll a,ll b){
	return (((fac[a]%mod)*(qpow(fac[b],mod-2)%mod)%mod)*(qpow(fac[a-b],mod-2)%mod)%mod);
}
int main(){
	scanf("%d %d",&n,&k);//多余的输入在牛客上面可以不输 
	fac[0]=1;//初始化x! 
	for(int i=1;i<=max(n,k);i++){
		fac[i]=((fac[i-1]%mod)*(i%mod))%mod;
	} 
	for(int i=1;i<=min(n,k);i++){
		ans=(ans+((c(n-1,i-1)%mod)*(c(k,i)%mod)%mod)*(fac[i]%mod))%mod;
	} 
	printf("%lld\n",ans);
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值