CF53E Dead Ends

CF53E Dead Ends

给定 \(n\) 个点 \(m\) 条边的无向图 \(G\),求恰好有 \(k\) 个叶子节点的生成树个数。

\(3 \leq n \leq 10\)\(n-1 \leq m \leq \dfrac{n(n-1)}{2}\)\(2 \leq k \leq n-1\)

考虑点集 \(S_1\) 的最小生成树中叶子节点构成的集合为 \(S_2\),这样的结构是可以 dp 的。状态可以用三进制表示,第 \(i\) 位为 \(0\) 表示 \(i \not \in S_1\),第 \(i\) 位为 \(1\) 表示 \(i \in S_1\)\(i \not \in S_2\),第 \(i\) 位为 \(2\) 表示 \(i \in S_2\)

考虑 dp 转移。这里我们采用正推的方式。具体地,我们考虑 \(dp_i\) 对其他值的贡献。考虑 \(i\) 对应的点集 \(S_1\),可以分别从 \(S_1\) 内与 \(S_1\) 外选出点 \(u\) 和点 \(v\) 连边,容易发现连边后 \(u\) 变成非叶子节点,\(v\) 变成叶子节点。现在的问题是 \(dp_i\) 会算重,这里我们采取最简单的方式,考虑 \(dp_i\) 会被 \(S_1\) 的所有叶子贡献一遍,考虑将 \(dp_i\) 除以 \(S_1\) 对应的叶子个数即可。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=10,M=N*(N-1)/2,V=59049;
long long dp[V];
int pow_3[N+10],flag[N];
struct Edge{
	int u,v;
}edge[M];
int main(){
	int n,m,k;
	scanf("%d %d %d",&n,&m,&k);
	pow_3[0]=1;
	for(int i=1;i<=n;i++){
		pow_3[i]=pow_3[i-1]*3;
	}
	for(int i=0;i<m;i++){
		scanf("%d %d",&edge[i].u,&edge[i].v);
		edge[i].u--,edge[i].v--;
	}
	long long ans=0;
	for(int Node_tar=2;Node_tar<=n;Node_tar++){
		for(int i=1;i<pow_3[n];i++){
			int Node=0,LeaF=0;
			for(int j=0;j<n;j++){
				flag[j]=i/pow_3[j]%3;
				if(flag[j]==1){
					Node++;
				}
				if(flag[j]==2){
					Node++;
					LeaF++;
				}
			}
			if(!LeaF  ||  Node!=Node_tar){
				continue;
			}
			if(Node==2  &&  LeaF==2){
				dp[i]=2;
			}
			dp[i]/=LeaF;
			if(Node>=2){
				for(int j=0;j<m;j++){
					int u=edge[j].u,v=edge[j].v;
					if(flag[u]  &&  !flag[v]){
						if(flag[u]==1){
							dp[i+2*pow_3[v]]+=dp[i];
						}
						else{
							dp[i-pow_3[u]+2*pow_3[v]]+=dp[i];
						}
					}
					if(flag[v]  &&  !flag[u]){
						if(flag[v]==1){
							dp[i+2*pow_3[u]]+=dp[i];
						}
						else{
							dp[i-pow_3[v]+2*pow_3[u]]+=dp[i];
						}
					}
				}
			}
			if(Node==n  &&  LeaF==k){
				ans+=dp[i];
			}
		}
	}
	printf("%lld",ans);
	return 0;
}
posted @ 2025-10-27 15:09  Oken喵~  阅读(1)  评论(0)    收藏  举报