题目:传送门
思路:状压dp(i,j) 表示以最低位1的位号作起点(避免重复,环上任意点都能做起点,因此在这里规定一个起点,而且枚举状态时,会优先枚举低位再枚举高位),经过的点集为i,以j作为当前路径临时终点的方案数, 这里的路径可以理解为“不完整的环” “假设环”;
转移方程 :若 j 和 k(k>=起点) 之间有边 ,则 dp(i,j) ——> dp(i|1<<k,k) ,在路径末,加入k点 ;(如果k就是起点,那么就把dp值加入答案中,这里要注意类似1-2-1的环(实际上就是无向边)是不合法的)
详细解释见代码:

1 #include<bits/stdc++.h> 2 /* 3 #include<cstdio> 4 #include<cmath> 5 #include<cstring> 6 #include<vector> 7 #include<cctype> 8 #include<queue> 9 #include<algorithm> 10 #include<map> 11 #include<set> 12 */ 13 #pragma GCC optimize(2) 14 using namespace std; 15 typedef long long LL; 16 typedef pair<int,int> pii; 17 typedef pair<double,double> pdd; 18 const int N=20; 19 const int M=3005; 20 const int inf=0x3f3f3f3f; 21 const LL mod=1e9+7; 22 const double eps=1e-9; 23 const long double pi=acos(-1.0L); 24 #define ls (i<<1) 25 #define rs (i<<1|1) 26 #define fi first 27 #define se second 28 #define pb push_back 29 #define mk make_pair 30 #define mem(a,b) memset(a,b,sizeof(a)) 31 LL read() 32 { 33 LL x=0,t=1; 34 char ch; 35 while(!isdigit(ch=getchar())) if(ch=='-') t=-1; 36 while(isdigit(ch)){ x=10*x+ch-'0'; ch=getchar(); } 37 return x*t; 38 } 39 LL dp[1<<N][N];//dp(i,j) , 表示在i的状态下(路径经过的点),以 i的最低位的位号作起点,以j作为临时终点的 路径个数 "假设环"个数 (先假设这个路径能成为环,在dp加点验证) 40 int n,m; 41 int pic[N][N]; 42 int qs(int x) 43 { 44 int i; 45 for(i=0;(1<<i&x)==0;i++); 46 return i; 47 } 48 int main() 49 { 50 n=read(),m=read(); 51 for(int i=1;i<=m;i++) 52 { 53 int x=read(),y=read(); 54 pic[x-1][y-1]=1; 55 pic[y-1][x-1]=1; 56 } 57 for(int i=0;i<n;i++) dp[1<<i][i]=1;//赋初值(创建以i为起点的路径"假设环") 58 int lim=1<<n; 59 LL ans=0; 60 for(int i=1;i<lim;i++) 61 { 62 int s=qs(i);//s为当前状态下的路径起点(假设s是某个环的起点,再通过向路径中加点来验证,我们可以把这个路径称作"假设环") 63 for(int e=s;e<n;e++) //枚举路径终点(假设环的临时终点) 64 { 65 if(dp[i][e]==0) continue;//这个语句的判定包括了 对于(1<<e&i)的判定,由转移方程可知 dp[x][k] 其中 (1<<k&x)==1 ,这样的dp值才可能被访问到 66 //if(1<<e&i) 67 for(int k=s;k<n;k++) //向路径中加入新点,且这个新点的编号不能比s小,因为s是"假设环"的起点不能被替代【避免出现类似于 1-2-3-1 和 2-3-1-2 重复的情况】 68 { 69 if(!pic[e][k]) continue; 70 if(1<<k&i) 71 { 72 if(k==s) ans+=dp[i][e]; 73 } 74 else dp[i|1<<k][k]+=dp[i][e];//向"假设环"中加点 75 } 76 } 77 } 78 printf("%lld ",(ans-m)/2); 79 //可以在dp的过程中除掉只有两个点的环(只有第s、e位上为1的环,相当于边数),也可以直接在答案中减m //if(k==s&&(1<<s|1<<e)!=i) 80 //除以2是因为 ,长度>=3的环会被算两次,例如1-2-3-4-1 和 1-4-3-2-1 ;而 1-2-1 只是会被算一次(自行模拟) 81 return 0; 82 }