思路
比较小,刚开始直接爆搜,从第个点出发,只去比当前点编号大的点,一旦回到起点,就维护一次答案。那么所有的方案数就是的全排列,复杂度达到级别。
正解是状压dp,设为经过点集中的所有点,到达点时的方案数(默认点集最右边的点为起点,显然可以用得到)。考虑一个与点相连的点,如果是起点,就可以直接用维护答案(也就是从出发走了一些路到达之后又回到了点),否则如果不在点集中,说明可以通过当前这条路到达,将维护进中。
但是这样会有重复计算,比如长度只有的环也会被维护进答案,总数有个(每个单独的边都会被算作一个环)。另外,每个长度大于的环都会被记录两遍(顺时针和逆时针),最后要除以。
代码
#include<bits/stdc++.h>
#define rep(i,st,ed) for(int i=st;i<=ed;++i)
#define bl(u,i) for(int i=head[u];i;i=e[i].nxt)
#define LLM LONG_LONG_MAX
#define LLm LONG_LONG_MIN
#define pii pair<ll,ll>
typedef long long ll;
typedef double db;
using namespace std;
const ll INF=0x3f3f3f3f;
void read() {}
void say() {}
void say_() {}
template <typename T, typename... T2>
inline void read(T &_, T2 &... oth)
{
int __=0;
_=0;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-')
__=1;
ch=getchar();
}
while(isdigit(ch))
{
_=_*10+ch-48;
ch=getchar();
}
_=__?-_:_;
read(oth...);
}
template <typename T>
void Out(T _)
{
if(_<0)
{
putchar('-');
_=-_;
}
if(_>=10)
Out(_/10);
putchar(_%10+'0');
}
template <typename T, typename... T2>
inline void say(T _, T2... oth)
{
Out(_);
putchar('\n');
say(oth...);
}
template <typename T, typename... T2>
inline void say_(T _, T2... oth)
{
Out(_);
putchar(' ');
say_(oth...);
}
/*#################################*/
const ll N=19,M=405;
ll n,m,tot,ans;
ll f[1ll<<N|1][N],G[N][N];
int main()
{
read(n,m);
ll u,v;
rep(i,1,m)
{
read(u,v);
G[u][v]=G[v][u]=1;
}
rep(i,0,n-1)
f[1ll<<i][i+1]=1;
ll lim=(1ll<<n)-1;
rep(i,1,lim)
{
rep(u,1,n)
{
if(!f[i][u])
continue;
ll lb=ll(log2(i&-i))+1;
rep(v,lb,n)
{
if(u!=v && G[u][v])
{
if(v==lb)
ans+=f[i][u];
else if(!(i&(1ll<<(v-1))))
f[i|(1ll<<(v-1))][v]+=f[i][u];
}
}
}
}
ans-=m;
ans>>=1;
say(ans);
}