Codeforces C. A Simple Task(状态压缩dp)
题目描述:
Given a simple graph, output the number of simple cycles in it. A simple cycle is a cycle with no repeated vertices or edges.
The first line of input contains two integers n and m (1 ≤ n ≤ 19, 0 ≤ m) – respectively the number of vertices and edges of the graph. Each of the subsequent m lines contains two integers a and b, (1 ≤ a, b ≤ n, a ≠ b) indicating that vertices a and b are connected by an undirected edge. There is no more than one edge connecting any pair of vertices.
Output the number of cycles in the given graph.
4 6
1 2
1 3
1 4
2 3
2 4
3 4
7
The example graph is a clique and contains four cycles of length 3 and three cycles of length 4.
思路:
题目要求是给一个图,判断这个图里有几个环,一个环内,每两个顶点间只能由一条边相连。
刚开始:深度优先搜索,对每个顶点做一次深搜,第二次遇到刚开始的顶点,环ans++;最后由于重复的环ans/=2,得出答案。然而,这样是会超时的。
原因呢,是大量的重复计算,如下图的解答树:1-3-2-4-1和1-4-2-3-1实际上是一样的环,同一个顶点的环有重复,不同顶点为起点的环也可能重复
然后,dp是不可能dp的,这辈子都不会dp,只能勉强用暴搜来维持生活这样子,
考虑状态压缩dp,用dp[s][u]来表示,现在的点的访问状态用s的二进制形式储存着,dp[s][u],表示在这个访问状态下,到达点u的方式有多少种,其中s二进制的最右端不为零的位置对应的是路径的起点,(如100100,起点为3)怎么算起点呢?记得上篇博客介绍过__builtin_ctz来计算二进制末尾0的个数start=__builtin_ctz(s)+1就是起点的位置。
考虑初始条件,d[1<<(i-1)][i]=1(表示只有自己点访问的情况下,自己到自己点的方式只有一种)。
接着是状态转移,状态转移可写作dp[s][u] = sum(dp[s][i]|G[i][u]==1),方向是从i到u,
也可以写成是方向u到i,dp[s'][i]+=dp[s][u](G[u][i]==1&&i!=start&&1<<(i-1)&s==0)(s'=1<<(i-1)|s),即当i与u有边,而且i不是起点,而且状态s中的i个位置上i没有出现过,那么更新s‘i的数值。
终点呢,是如果状态转移的时候,发现从u到i结果i就是起点,而且状态s对应的i位上已经出现过一次i,说明已经形成了一个环。这是ans+=dp[s][u]。
为了方便理解,举一个栗子
现在有图:GFEDCBA,其中只有ACEF形成一个环
G:GFEDCBA
s’:0 1 1 0 1 0 1(s的二进制表示),假设现在状态就是这个s’,我已经从1(起点)开始,初始状态为s(不是s'),不断进行状态转移得到了这个未来会遍历到的状态(s'),
等到我终于遍历到这个s'的时候,我遍历图,遇到F点,发现他和A点有边相连,而且A 是顶点,而且A点已经访问过,那么!一个环到了!
注意的是这个算法会把两个直接有边相连的顶点看成一个环,(想想为什么),比如CA相连,101在以1为起点u为3是发现3有连1的边,而且出现过,而且是起点,就以为是一个环了。所以最后有几个边直接相连就多了几个环,减去就是,还有一点,换会重复两遍,一个方向一遍,要减掉
注意:由于n的数字很小,用比较tricky的思维来看,应该找不到一个多项式算法,因此我们可以设计一个阶层或指数级的算法。有兴趣的同学可以证明该问题是个NP问题。
一个环是由若干个节点以及节点的顺序决定的。若用最暴力的方法统计n个节点的无向图中环的个数,则根据圆排列公式需要枚举O(∑ni=3(i!2i))
个环,每个环用O(n)的时间复杂度检查每个环是否真的存在,因此,总的时间复杂度则为O(n!)。由于该问题的n最大值是19,而19!是一个庞大的数字,所以阶层复杂度的算法是不能够被接受的。
所以数据要开long long
代码:
1 #include <iostream> 2 #define max_n 20 3 using namespace std; 4 int n; 5 int m; 6 int G[max_n][max_n]; 7 long long dp[1<<max_n][max_n]; 8 long long ans = 0; 9 int st(int n) 10 { 11 return __builtin_ctz(n)+1; 12 } 13 int main() 14 { 15 //cout << __builtin_ctz(4)+1 << endl; 16 cin >> n >> m; 17 for(int i = 0;i<m;i++) 18 { 19 int f,t; 20 cin >> f >> t; 21 G[f][t] = G[t][f] = 1; 22 } 23 for(int i = 1;i<=n;i++) 24 { 25 dp[1<<(i-1)][i] = 1; 26 } 27 long long S = (1<<n)-1; 28 //cout << S << endl; 29 for(int s = 1;s<=S;s++) 30 { 31 for(int u = 1;u<=n;u++) 32 { 33 if(dp[s][u]==0) //没有方法可以按着s到u 34 { 35 continue; 36 } 37 int start = st(s); 38 //cout << start << endl; 39 for(int i = start;i<=n;i++) 40 { 41 if(G[i][u]) 42 { 43 if((s&(1<<(i-1)))&&i==start) 44 { 45 ans += dp[s][u]; 46 } 47 else if(!(s&(1<<(i-1)))) 48 { 49 long long ss = s|(1<<(i-1)); 50 dp[ss][i] += dp[s][u]; 51 } 52 } 53 } 54 } 55 } 56 ans -= m; 57 ans /= 2; 58 cout << ans << endl; 59 return 0; 60 }
以上还不明白?正常,我可能也没讲太清楚,推荐给你:
餃子,Codeforces A Simple Task 统计简单无向图中环的个数,https://blog.csdn.net/fangzhenpeng/article/details/49078233
还有:C20191904,CodeForces - A Simple Task,https://blog.csdn.net/C20191904/article/details/81513904