Codeforces C. A Simple Task(状态压缩dp)

题目描述:

 A Simple Task
time limit per test
2 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

Given a simple graph, output the number of simple cycles in it. A simple cycle is a cycle with no repeated vertices or edges.

Input

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

Output the number of cycles in the given graph.

Examples
Input
Copy
4 6
1 2
1 3
1 4
2 3
2 4
3 4
Output
Copy
7
Note

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

 

posted @ 2019-07-24 15:48  小张人  阅读(423)  评论(0编辑  收藏  举报
分享到: