原题传送门
题意简述:
给定n个点和m条边,接下来m行每行x,y表示两个连通的点,输出图中的环数
题目分析:
一看数据n<=19,马上想到状压DP记录状态
设 f[i][j]表示状态为i,结束点为j,起点为状态i中lowbit最小的点的路径中的环数,枚举下一个节点,当两个节点相同时证明出现了一个环,由于无向图的情况,会出现::
- 同一个环由于可以从两个方向遍历所以会被记录两次
- 一条边和两个端点出现的非法环(两个点因可以相互到达而被误判成环的情况)
故最后的答案ans = (ans - m) / 2
code:
/* Codeforces11D -- A Simple Task */
#include <bits/stdc++.h>
#define int long long
int n, m, ans;
int f[1 << 19][19];
bool mp[25][25];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch))
f = (ch == '-') ? -1 : 1, ch = getchar();
while (isdigit(ch))
x = x * 10 + (ch - '0'), ch = getchar();
return x * f;
}
main()
{
// freopen("in.txt", "r", stdin);
n = read(), m = read();
int x, y;
int MaxState = 1 << n;
for (int i = 1; i <= m; i++)
x = read() - 1, y = read() - 1, mp[x][y] = mp[y][x] = true;
for (int i = 0; i < n; i++)
f[1 << i][i] = 1; /* 初始化 */
for (int i = 1; i <= MaxState; i++)
for (int j = 0; j < n; j++)
{
if (!f[i][j]) /* 无环图 */
continue;
for (int k = 0; k < n; k++) /* 枚举下一节点 */
{
if (!mp[j][k]) /* 不连通就直接跳过 */
continue;
if ((i & -i) > (1 << k)) /* lowbit > (1 << k)表示k点编号小于i点编号,但是起点不可以更改 */
continue;
if ((1 << k) & i) /* 构成一个环 */
{
if ((1 << k) == (i & -i)) /* 这个环回到了起点 */
ans += f[i][j];
}
else
f[i | (1 << k)][k] += f[i][j]; /* 状态转移 */
}
}
std::cout << (ans - m) / 2 << '\n';
}