考虑状态压缩动态规划。记 f(s,i) 表示满足当前经过结点集合为 s,且现在在结点 i 上,且第一个结点为结点集合 s 中 编号最小的那个 的路径条数。
对于状态 f(s,i),枚举下一个结点 u。若 u 在集合 s 中且是编号最小的那个(即起点),就将答案 A 加上 f(s,i)。若 u 不在 s 中,就将 f(s,i) 加上 f(s∪{u},u)。
这样会把二元环(即重边)也算上,并且每个非二元环会被计算两次(因为固定起点可以向两个方向走),所以答案为 2A−m,其中 m 表示边数。时间复杂度 O(2nm)。
示例代码
#include <iostream>using namespace std;int n, m;struct Edge { int to, nxt;} edge[500];int cntEdge, head[20];void addEdge(int u, int v) { edge[++cntEdge] = {v, head[u]}, head[u] = cntEdge;}long long answer, dp[1 << 19][20];int main() { cin.tie(nullptr)->sync_with_stdio(false); cin >> n >> m; for (int i = 1; i <= m; i++) { int u, v; cin >> u >> v; addEdge(u, v); addEdge(v, u); } for (int i = 1; i <= n; i++) dp[1 << (i - 1)][i] = 1; for (int s = 1; s < (1 << n); s++) for (int i = 1; i <= n; i++) { if (!dp[s][i]) continue; for (int j = head[i]; j; j = edge[j].nxt) { int u = i, v = edge[j].to; if ((s & -s) > (1 << (v - 1))) continue; if (s & (1 << (v - 1))) { if ((s & -s) == (1 << (v - 1))) answer += dp[s][u]; } else dp[s | (1 << (v - 1))][v] += dp[s][u]; } } cout << (answer - m) / 2 << '\n'; return 0;}
#include <iostream>using namespace std;int n, m, total;int deg[200001], u[200001], v[200001];bool vis[200001];struct Edge { int to, nxt;} edge[200001];int cntEdge, head[100001];void addEdge(int u, int v) { edge[++cntEdge] = {v, head[u]}, head[u] = cntEdge;}int main() { cin.tie(nullptr)->sync_with_stdio(false); cin >> n >> m; for (int i = 1; i <= m; i++) cin >> u[i] >> v[i], deg[u[i]]++, deg[v[i]]++; for (int i = 1; i <= m; i++) { if ((deg[u[i]] == deg[v[i]] && u[i] > v[i]) || deg[u[i]] < deg[v[i]]) swap(u[i], v[i]); addEdge(u[i], v[i]); } for (int u = 1; u <= n; u++) { for (int i = head[u]; i; i = edge[i].nxt) vis[edge[i].to] = true; for (int i = head[u]; i; i = edge[i].nxt) { int v = edge[i].to; for (int j = head[v]; j; j = edge[j].nxt) { int w = edge[j].to; if (vis[w]) total++; } } for (int i = head[u]; i; i = edge[i].nxt) vis[edge[i].to] = false; } cout << total << '\n'; return 0;}
这个图形是两个三元环共用了一条边形成的。所以我们先跑一遍三元环计数,统计出一条边上三元环的数量,然后枚举共用的那条边,设有 x 个三元环中有此边,那么对答案的贡献就是 (2x)。
时间复杂度 O(mm)。
示例代码
#include <cstring>#include <iostream>using namespace std;int n, m, total;int deg[200001], u[200001], v[200001];int edgeId[200001], cnt[200001];struct Edge { int to, nxt;} edge[200001];int cntEdge, head[100001];void addEdge(int u, int v) { edge[++cntEdge] = {v, head[u]}, head[u] = cntEdge;}int main() { while (cin >> n >> m) { cntEdge = total = 0; memset(deg, 0, sizeof deg); memset(head, 0, sizeof head); for (int i = 1; i <= m; i++) cin >> u[i] >> v[i], deg[u[i]]++, deg[v[i]]++; for (int i = 1; i <= m; i++) { if ((deg[u[i]] == deg[v[i]] && u[i] > v[i]) || deg[u[i]] < deg[v[i]]) swap(u[i], v[i]); addEdge(u[i], v[i]); } for (int u = 1; u <= n; u++) { for (int i = head[u]; i; i = edge[i].nxt) edgeId[edge[i].to] = i; for (int i = head[u]; i; i = edge[i].nxt) { int v = edge[i].to; for (int j = head[v]; j; j = edge[j].nxt) { int w = edge[j].to; if (edgeId[w]) cnt[i]++, cnt[j]++, cnt[edgeId[w]]++; } } for (int i = head[u]; i; i = edge[i].nxt) edgeId[edge[i].to] = 0; } for (int i = 1; i <= m; i++) total += cnt[i] * (cnt[i] - 1) / 2, cnt[i] = 0; cout << total << '\n'; } return 0;}
#include <iostream>#include <vector>using namespace std;int n, m, deg[100001], cnt[100001];vector<int> E[100001], E1[100001];long long total;int main() { cin.tie(nullptr)->sync_with_stdio(false); cin >> n >> m; for (int i = 1; i <= m; i++) { int u, v; cin >> u >> v; E[u].push_back(v); E[v].push_back(u); deg[u]++, deg[v]++; } for (int u = 1; u <= n; u++) for (int v : E[u]) if (deg[u] > deg[v] || (deg[u] == deg[v] && u > v)) E1[u].push_back(v); for (int a = 1; a <= n; a++) { for (int b : E1[a]) for (int c : E[b]) { if (deg[a] < deg[c] || (deg[a] == deg[c] && a <= c)) continue; total += cnt[c]++; } for (int b : E1[a]) for (int c : E[b]) cnt[c] = 0; } cout << total << '\n'; return 0;}
下面考虑第四种情况。考虑枚举度数为 2 的点 x,再枚举与它相邻的一个结点 y 作为度数为 3 的那个点。此时对答案的贡献为 [d(x)−1]⋅(2d(y)−1)。但是注意到 y 的相邻节点可能会和 x 的相邻结点重合,此时的图形等价于第三种情况。但是每种多算的第三种情况都会被多算两次(因为有两个度数为 3 的点),所以应该减去第三种情况数目的两倍。
对于最后一种情况,先枚举中间的点 x,那么容易发现对答案的贡献是
y∈sonx∑z∈sonx∑[d(y)−1]⋅[d(z)−1].
同样地,这其中有多算的部分。设 y 的相邻结点为 s,z 的相邻结点为 t,那么思考后发现多算的有如下几种情况: