考虑状压dp:f[u][p]表示当前点为u,状态为p的方案数,用记忆化搜索来实现。 为了避免重复情况的出现,我们需要再枚举一个状态中的最小节点:在后来的所有进入环中的节点均需大于该状态的最小节点。 由于一个环,可以正着走和倒着走,所以最后方案数要除以2。 #include <bits/stdc++.h> #define int long long using namespace std; const int N=19; int n,m,x,y,ans; int a[N][N],bin[N],f[N][1<<N]; bool vis[N][1<<N]; int dfs(int u,int p) //当前点为u,状态为p,所有集合的数要求 >= __builtin_ffsll(p)-1 { if (vis[u][p]) return f[u][p]; vis[u][p]=true; int sum=__builtin_popcountll(p),minn=__builtin_ffsll(p)-1; for (register int i=0; i<n; ++i) if (a[u][i]) { if (bin[i]&p) { if (sum>2 && i==minn) f[u][p]++; //如果枚举的一个点属于当前集合,且是当且集合的最小点,那么就说明回到起点了,形成一个环 //但是需要的条件是集合大小大于2,因为题意中没有重复的两条边和自环,所以不会存在小于3个点的环 //在计算时,会出现两点一边的情况,就是sum=2的情况,所以这里需要判断sum与2的大小关系 } else { if (i>minn) f[u][p]+=dfs(i,p|bin[i]); //如果枚举的一个点不属于当前集合,那么就继续递归下去 } } return f[u][p]; } signed main(){ scanf("%lld%lld",&n,&m); for (register int i=1; i<=m; ++i) { scanf("%lld%lld",&x,&y); x--; y--; a[x][y]=a[y][x]=1; } for (register int i=0; i<=18; ++i) bin[i]=1ll<<i; for (register int i=0; i<n; ++i) ans+=dfs(i,bin[i]); printf("%lld\n",ans>>1ll); //由于一个环,可以正着走和倒着走,所以方案数要除以2 return 0; }