题目链接
题目大意
需要你将一棵树分成若干个连通块,连通块上的颜色相同,不同连通块之间的颜色不同。
题目思路
前言
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(n−1,x−1)∗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;
}