CF 11D - A Simple Task (状压DP 求简单环个数)

题目:传送门

思路:状压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\n",(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 }
View Code

 

posted @ 2020-05-10 21:54  DeepJay  阅读(179)  评论(0编辑  收藏  举报