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;
}

浙公网安备 33010602011771号