題目描述:
shy有一顆樹,樹有n個結點。有k種不同顏色的染料給樹染色。一個染色方案是合法的,當且僅當對於所有相同顏色的點對(x,y),x到y的路徑上的所有點的顏色都要與x和y相同。請統計方案數。
輸入描述:
第一行兩個整數n,k代表點數和顏色數;
接下來n-1行,每行兩個整數x,y表示x與y之間存在一條邊;
輸出描述:
輸出一個整數表示方案數(mod 1e9+7)。
示例1
輸入
4 3
1 2
2 3
2 4
輸出
39
備註:
對於30%的數據,n≤10, k≤3;
對於100%的數據,n,k≤300。
思路:
組合數學做法:
從[1,k]枚舉所有聯通塊(聯通塊滿足要求),然後求出每種聯通塊能夠顏色的方案數量。
怎麼枚舉所有聯通塊?
從邊的角度來考慮,我們每次枚舉 i 個聯通塊,就會少i - 1 條邊,那麼從n - 1條邊刪掉i - 1條邊就是它的方案數量。這個方案數與順序無關所以是C(n - 1, i - 1),然後每種方案數用k 種顏色去染 i 個聯通塊就是排列問題了,所以最終答案就是
時間複雜度O(n)。
代碼:
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
ll mod = 1e9 + 7;
ll qp(ll a,ll b, ll p){ll ans = 1;while(b){if(b&1){ans = (ans*a)%p;--b;}a = (a*a)%p;b >>= 1;}return ans%p;}
ll Inv(ll x) { return qp(x,mod-2,mod);}
ll C(ll n,ll m){if (m>n) return 0;ll ans = 1;for (int i = 1; i <= m; ++i) ans=ans*Inv(i)%mod*(n-i+1)%mod;return ans%mod;}
ll A(ll n,ll m,ll mod){ll sum=1; for(int i=n;i>=n-m+1;i--) sum=(sum*i)%mod; return sum%mod;}
void solved(){
ll n,k;cin>>n>>k;
ll ans = 0;
for(int i = 1; i <= k; i++){
ans += (C(n - 1, i - 1)%mod * A(k,i,mod) % mod)%mod;
}
cout<<ans % mod<<endl;
}
int main(){
solved();
return 0;
}
DP做法:
一開始看的這個題,感覺不知道怎麼搞,一開始想的是dfs暴力搞一下,但是要檢查(u,v)顏色是不是相同就感覺寫不出來。。。
然後看了一下題解是dp,定義
dp[i][j]:前i個節點從k種顏色中取j種顏色取染色的方案數。(一開始以爲從前i個節點用j種顏色。。)
轉移方程:dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1] * (k - (j - 1))
大概意思是:考慮第i個節點從k種顏色選j種的方案數會等於跟它父親節點顏色保持一致(滿足條件),或者是用新的顏色那麼就是原來父親節點用了的顏色數量 * (總顏色數量 - 父親節點用了的數量)這樣相加就是dp[i][j]的數量了。
我感覺這題好像可以用組合數學的方法做,先想一想,想出來了再更新。
#include<bits/stdc++.h>
using namespace std;
const long long int mod = 1000000000 + 7;
long long int dp[10000][10000];
//dp[i][j]:前i個節點從k種顏色選擇j種染樹的方案數量
void solved(){
long long int n,k;cin>>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));
dp[i][j] %= mod;
}
}
long long int ans = 0;
for(int i = 1; i <= k; i++){
ans += dp[n][i];
ans %= mod;
}
cout<<ans;
}
int main(){
solved();
return 0;
}