[Nowcoder] Niuke練習コンペティション1Bツリー|ツリーdp、組み合わせ論

本旨:

shyにはツリーがあり、ツリーにはn個のノードがあります。木を染めるために異なる色のkの染料があります。同じ色のすべてのポイントペア(x、y)について、xからyへのパス上のすべてのポイントの色がxおよびyと同じでなければならない場合に限り、配色は合法です。プランの数を数えてください。

質問のアイデア:

各色は接続されたブロックであるため、質問の要件を変換します。

それ以外の場合、要件は満たされていません

したがって、このツリーをkkに分割することを検討できます。k個の接続されたブロックのスキームの数はいくつですか

kkに分割する場合k個の接続されたブロックの各プランにはmmがありますm色の場合、スキームの数は当然次のようになります。Amk A_m ^ kAmK

したがって、ツリーを1 .... k 1 .... kに分割する必要があります。1 、K用のプログラム通信ブロックの数、各KKkans = ans + cal(k)∗ A mk ans = ans + cal(k)* A_m ^ ka n s=a n s+c a l k AmK

この問題は解決されました

ツリーをk個の接続されたブロックに分割するためのスキームの数を見つける方法は?

2つの方法:

1.树形dp

dp [u] [k] dp [u] [k] d p [ u ] [ k ]は、uをルートとするサブツリーを表し、kkに分割されます。k個の接続されたブロックのスキームの数

次に、状態遷移は明らかに、各uuに対してです。uの子eee

このサブツリーには2つの状況があります。

  • 最後に接続されたブロックに組み込む
  • 最後に接続されたブロックに収まらない

だからあります:

		for(int i=1;i<=min(sz[u],m);i++){
    
    ///枚举子树大小
			for(int k=1;k<=min(sz[e],m);k++){
    
    
				if(i+k-1<=m) t[u][i+k-1] = (t[u][i+k-1] + (dp[u][i] * dp[e][k]) )%mod; 
			}
		}
		for(int i=1;i<=min(sz[u],m);i++){
    
    ///枚举子树大小
			for(int k=1;k<=min(sz[e],m);k++){
    
    
				if(i+k<=m) t[u][i+k] = (t[u][i+k] + (dp[u][i] * dp[e][k]) )%mod; 
			}
		}
		for(int k=1;k<=m;k++) dp[u][k] = t[u][k],t[u][k] = 0;

したがって、複雑さはO(n ∗ m)O(n * m)です。O nm a

2.2。组合数学

ツリーをkkに分割することを検討してくださいk個の接続されたブロックは木を切り落とすだけです(k − 1)(k-1)k1 エッジ

したがって、n − 1n-1でn1エッジタイプk− 1 k-1k1エッジの場合、解の数はC n − 1 k − 1 C_ {n-1} ^ {k-1}です。Cn 1K - 1

そして、A mk A_m ^ kでAmK掛けるだけ

最後に、コードdfs(1、1)dfs(1,1)を添付しますd f s 1 1 した後、DP [1]〜[I] DP [1]〜[I]d p [ 1 ] [ i ]は、i個の接続されたブロックに分割された解の数を表します

コード:

/*** keep hungry and calm CoolGuang!  ***/
#pragma GCC optimize("Ofast","unroll-loops","omit-frame-pointer","inline")
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#include<stdio.h>
#include<queue>
#include<algorithm>
#include<string.h>
#include<iostream>
#define debug(x) cout<<#x<<":"<<x<<endl;
#define dl(x) printf("%lld\n",x);
#define di(x) printf("%d\n",x);
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
const ll INF= 1e17+7;
const ll maxn =2e5+700;
const ll mod= 1e9+7;
const ll up = 1e13;
const double eps = 1e-9;
template<typename T>inline void read(T &a){
    
    char c=getchar();T x=0,f=1;while(!isdigit(c)){
    
    if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){
    
    x=(x<<1)+(x<<3)+c-'0';c=getchar();}a=f*x;}
ll n,m,p;
vector<int>v[maxn];
ll dp[305][305];
ll t[305][305];///代替分组背包
ll sz[maxn];
ll s[2005][2005];
ll cal(ll x,ll y){
    
    
 	if(x<y) return 0;
    if(y == 0 || x == y) return s[x][y] = 1;
    if(y == 1) return s[x][y] = x%mod;
    if(~s[x][y]) return s[x][y];
    return s[x][y] = (cal(x-1,y) + cal(x-1,y-1))%mod;
}
void dfs(int u,int fa){
    
    
	sz[u] = 1;
	dp[u][1] = 1;
	for(int e:v[u]){
    
    
		if(e == fa) continue;
	
		///对于新来的任何一个子数 都有与当前合并 和 不与当前合并
		dfs(e,u);

		for(int i=1;i<=min(sz[u],m);i++){
    
    ///枚举子树大小
			for(int k=1;k<=min(sz[e],m);k++){
    
    
				if(i+k-1<=m) t[u][i+k-1] = (t[u][i+k-1] + (dp[u][i] * dp[e][k]) )%mod; 
			}
		}
		for(int i=1;i<=min(sz[u],m);i++){
    
    ///枚举子树大小
			for(int k=1;k<=min(sz[e],m);k++){
    
    
				if(i+k<=m) t[u][i+k] = (t[u][i+k] + (dp[u][i] * dp[e][k]) )%mod; 
			}
		}
		for(int k=1;k<=m;k++) dp[u][k] = t[u][k],t[u][k] = 0;
		sz[u] += sz[e];
	}
}
ll A[maxn];
int main(){
    
    
	memset(s,-1,sizeof(s));
	read(n);read(m);
	for(int i=1;i<=n-1;i++){
    
    
		int x,y;read(x);read(y);
		v[x].push_back(y);
		v[y].push_back(x);
	}
	
	A[0] = 1;
	for(int i=1;i<=m;i++) A[i] = (A[i-1] * (m-i+1))%mod;
	ll ans = 0;
	for(int i=1;i<=m;i++){
    
    
		ans = (ans + (cal(n-1,i-1)*A[i])%mod)%mod;
	}
	printf("%lld\n",ans);
	return 0;
}
/***
ababd
abd
***/

おすすめ

転載: blog.csdn.net/qq_43857314/article/details/112277083