### Problem Statement There is an undirected grap...
Créé le : 21 juin 2025
Créé le : 21 juin 2025
There is an undirected graph with vertices and edges. does not contain self-loops, but may contain multi-edges. Vertices are numbered from to , and edges are numbered from to , with edge connecting vertices .
Find the number, modulo , of cycles contained in .
More formally, find the number, modulo , of subsets of the given edge set that satisfy the following condition.
下面给出一种 可以正确计数任意简单环(允许长度 = 2 的“平行边环”) 的通用做法。
它基于「起点固定 + 位运算子集 DP」——当 N≤20 左右时完全能在 2 秒内通过;
如果 N 很大,这个问题在一般图上属于 #P-hard,任何已知算法都需要指数时间。
建邻接矩阵(带重数)
textadj[u][v] = 与 u,v 之间的平行边条数 (u≠v)
由于图无自环,adj[u][u] = 0。
按照最小编号顶点去重
对每个起点 s
:
s
的顶点集合 mask
,并保证 s
是集合中编号最小的顶点。dp[mask][v]
表示:
“从
s
出发,经过mask
中所有点一次且仅一次,最终停在v
(v∈mask)” 的方案数
(路线按边 顺序 区分,同一条边的多条平行边也区分)。
状态转移
text初始: dp[1<<s][s] = 1 枚举 mask, 枚举末点 v∈mask 枚举下一点 u∉mask 且 adj[v][u] > 0 dp[mask | (1<<u)][u] += dp[mask][v] * adj[v][u]
复杂度 O(3^N)
(见 USACO-Guide Bitmask-DP 教程 usaco.guide)。
收尾形成环
当 mask
至少含 2 个顶点且 adj[v][s] > 0
时,
ans += dp[mask][v] * adj[v][s]
这里再乘一次平行边条数。
取模
每一步都对 MOD = 998244353
取模即可。
s → … → v → s
中每个顶点至多出现一次,因此得到的是简单环。s
为环中最小编号顶点,且构造过程中从未再回到比 s 小的顶点,所以任何简单环恰好被统计一次。u,v
之间有 c
条平行边,则环中可以自由选择这 c
条中的任意一条,所以每对顶点相乘时乘上 adj[u][v]
可计入所有可能。符号 | 含义 | 规模 |
---|---|---|
N | 顶点数 | ≤ 20 – 22 时可跑 |
M | 边数 (含重) | ≤ 400k (任意) |
2^N · N
u
,摊到平均 ≈ N/2O(N · 2^N)
≈ 3^N
,N=20 约 20 × 1 048 576 ≈ 2 × 10^7 次转移,1 秒内可过。vector<int>
/uint32_t
储存非零 dp
列即可,空间 O(N·2^N)
。cpp#include <bits/stdc++.h> using namespace std; const int MOD = 998244353; int addmod(int a, int b) { int s = a + b; return s >= MOD ? s - MOD : s; } int mulmod(long long a, long long b) { return int((a * b) % MOD); } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int N, M; if (!(cin >> N >> M)) return 0; /* 邻接矩阵,记录平行边条数 */ vector<vector<int>> adj(N, vector<int>(N, 0)); for (int i = 0, u, v; i < M; ++i) { cin >> u >> v; --u; --v; if (u == v) continue; // 无自环 ++adj[u][v]; ++adj[v][u]; } long long fullStates = 1LL << N; /* dp 是一个 vector<unordered_map<int,int>>,也可用平面数组 */ vector<vector<int>> dp(fullStates); // sparse:dp[mask][v] 按 v 索引 long long answer = 0; for (int s = 0; s < N; ++s) { /* 清空与 s 相关的状态 */ for (long long m = 0; m < fullStates; ++m) dp[m].clear(); long long startMask = 1LL << s; dp[startMask].resize(N); dp[startMask][s] = 1; for (long long mask = startMask; mask < fullStates; ++mask) { if (!(mask & startMask)) continue; // 必须含 s /* 找到 mask 中最大的顶点索引,加速子集枚举(可选) */ for (int v = 0; v < N; ++v) { int ways = (mask < fullStates && v < (int)dp[mask].size()) ? dp[mask][v] : 0; if (!ways) continue; /* 尝试闭环 */ if (__builtin_popcountll(mask) >= 2 && adj[v][s]) answer = addmod(answer, mulmod(ways, adj[v][s])); /* 向外扩展 */ for (int u = s + 1; u < N; ++u) if (!(mask & (1LL << u)) && adj[v][u]) { long long nmask = mask | (1LL << u); if (dp[nmask].empty()) dp[nmask].resize(N); dp[nmask][u] = addmod(dp[nmask][u], mulmod(ways, adj[v][u])); } } } } cout << (answer % MOD) << '\n'; return 0; }
dp[mask]
只在需要时分配;u > s
,即可确保不重复计数;mask
仅含两个顶点 (s,v)
时,仍能通过 “闭环” 语句被正确统计。祝 AC!