题目链接:
题意:
统计所有环的个数
思路:
状压DP
从一个点出发经过若干个点后能再次回到这个点,那么这个就是环。同时我们不关注如何到达这个点,只关心到这个点有多少种方法。
按照正常状压的思路,我们枚举所有状态。
表示到达状态为且当前在点的方案数。然后我们用j点去更新其他点。就是能够到达的点,且状态未经过,则此时我们就可以从走到,并且让 加上,如果状态经过了,则此时就构成了一个环。
为了避免重复计数,我们认为编号小的为环的起点,也就是说对于这个环,我们只把当作起点,即。由于是无向图,计算的时候也会计算,即计算了两次,同时每条边的两个顶点也会被当成环,如,但不会被计算两次,最后处理一下即可。
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include<iostream>
using namespace std;
const int N = 20;
typedef long long ll;
struct E{
int to, next;
}edge[1005];
int head[N], tot;
int n, m;
void addedge(int from, int to)
{
edge[++tot].to = to;
edge[tot].next = head[from];
head[from] = tot;
}
ll dp[1<<N][N];
int main()
{
memset(head, -1, sizeof(head));
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i++)
{
int u, v;
scanf("%d%d", &u, &v);
addedge(u - 1, v - 1);
addedge(v - 1, u - 1);
}
for (int i = 0; i <= n; i++)
dp[1 << i][i] = 1;
ll ans = 0;
for (int i = 1; i < (1<<n); i++)
{
for (int j = 0; j < n; j++)
{
if (!dp[i][j]) continue;
for (int k = head[j]; k != -1; k = edge[k].next){
int v = edge[k].to;//从j->v,由于不能重复计算,所以起点固定为小的开始,这个就是处理的方法
if ((i&(-i))>(1 << v)) continue;
if ((i >> v) & 1){
if ((i&(-i)) == (1 << v)) ans += dp[i][j];
}
else{
dp[i | (1 << v)][v] += dp[i][j];
}
}
}
}
printf("%lld\n", (ans - m) / 2);
return 0;
}
评论