Codeforces 11D A Simple Task 统计简单无向图中环的个数

题目表述

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.


构思

由于n的数字很小,用比较tricky的思维来看,应该找不到一个多项式算法,因此我们可以设计一个阶层或指数级的算法。有兴趣的同学可以证明该问题是个NP问题。

一个环是由若干个节点以及节点的顺序决定的。若用最暴力的方法统计n个节点的无向图中环的个数,则根据圆排列公式需要枚举O(ni=3(i!2i))个环,每个环用O(n)的时间复杂度检查每个环是否真的存在,因此,总的时间复杂度则为O(n!)。由于该问题的n最大值是19,而19!是一个庞大的数字,所以阶层复杂度的算法是不能够被接受的。


分析重复计算之处

如图所示的情况,节点s到节点j有一条边,节点i到节点j有一条边。

假设我们已经计算出节点s到节点i有3条简单路径。

接下来,我们要计算节点s到节点j的简单环有几条。根据前面阶层级的枚举算法,我们还要重新计算出节点s到节点i的3条简单路径,然后加上节点i到节点j的1条边,再加上节点s到节点j的1条边,构成3个简单环。

实际上,我们并不关心节点s到节点i的简单路径是怎样的,我们只关心节点s到节点i的简单路径的条数,就可以计算出节点s到节点j的简单环的个数。

Codeforces 11D A Simple Task 统计简单无向图中环的个数_第1张图片


算法设计

为了消除重复计算的部分,就很容易想到动态规划了。我们设计一个状态{[s][SET][i]}来记录起点s到终点i的简单路径的条数,其中SET表示经过的节点的集合。但由于圆排列的性质,这样的状态是有重复的。我们通过指定起点为SET中的最小序号点来消除圆排列带来的重复,状态变为{[SET][i]}。还要注意,即使这样定义状态,计算简单环个数的时候仍会将2个节点一条单边的情况当成环,也会将长度大于2的环正向计算一遍,反向计算一遍。所以我们还要进行后处理。

前向的状态转移方程可以写作:
dp[SET][j]=(iSET,ij)dp[SET][i]
但这样的方程并不利于统计简单环的个数。

后向的状态转移方程可以写作:
设简单环的个数用统计量cnt表示。对于dp[SET][i]状态,i的每一条边eij,jneighbor(i):若jSET则说明遇到了环,dp[SET][i]贡献给cnt;若jSET则说明获得一条简单路径,dp[SET][i]贡献给dp[SET][j]

后处理:
由于长度为2的简单环被统计了进去,所以cntm;又由于长度大于2的简单环被统计了2遍,所以(cntm)/2


代码

注意,我们分析过,最多可能有19!个简单环,所以数据类型得用int64。

/**
 * @authors:  zhenpeng.fang
 * @nickname: dumpling
 * @date:     2015-10-10 22:09:52
 */

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

#define mp make_pair
typedef long long int64;
const double eps = 1e-6;
const int64 INF64 = 10000000000000000LL;

const int N_NODE = 20;
const int N_EDGE = 400;

int64 cnt = 0;
int64 dp[1<int tail[N_NODE], es = 0;
int pre[N_EDGE], e[N_EDGE];

void add(int s, int t){
    ++es;
    pre[es] = tail[s];
    tail[s] = es;
    e[es] = t;
}

int main(){
    int n, m, s, t;
    scanf("%d%d", &n, &m);
    memset(tail, -1, sizeof(tail));
    memset(pre, -1, sizeof(pre));
    memset(e, -1, sizeof(e));
    for(int i = 0; i < m; ++i){
        scanf("%d%d", &s, &t);
        add(s - 1, t - 1);
        add(t - 1, s - 1);
    }

    for(int i = 0; i < n; ++i)
        dp[1<1;
    for(int ST = 1; ST < (1<for(int i = 0; i < n; ++i){
            if(dp[ST][i] == 0) continue;
            for(int edge = tail[i]; edge != -1; edge = pre[edge]){
                int j = e[edge];
                if((ST & (-ST)) > (1 << j)) continue;
                if((1 << j) & ST){
                    if((ST & (-ST)) == (1 << j)) 
                        cnt += dp[ST][i];
                }else{
                    dp[ST | (1 << j)][j] += dp[ST][i];
                }
            }
        }
    }
    cnt = (cnt - m) / 2;
    printf("%lld\n", cnt);
    return 0;
}

你可能感兴趣的:(codeforces,算法,dp,graph)