CF11D A Simple Task 题解
一、题目:
二、思路:
状压DP。
设\(f(x,s)\)表示走过点的集合为\(s\),当前在\(x\)这个点的所有链的方案数。为了避免重复,我们强制链的起点为链上所有点最小的那一个。
设边\((x,y)\)。\(y\)有以下几种情况。
- \(y\)小于\(s\)中编号最小的点。那么如果让\(s\)这条链和\(y\)连起来,那么新的链就会和以\(y\)为起点的链重复,不予考虑。
- \(y\)是\(s\)中编号最小的点。说明找到了环,令\(ans=ans+f(x,s)\)。
- \(y\)比\(s\)中编号最小的点大,但\(y\)不在\(s\)中。则\(f(y,s\cup \{y\})=f(y,s\cup \{y\})+f(x,s)\).
- \(y\)比\(s\)中编号最小的点大,但\(y\)在\(s\)中。虽然得到了一个环,但是这个环之前统计过,不予考虑。
然后我们发现\(ans\)多算了一部分。多算了只有两个点一条边的情况以及每个环多算了两次。
所以最终的答案就是\((ans-m)/2\)。
三、代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <bitset>
using namespace std;
inline int read(void) {
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return f * x;
}
const int maxn = 20;
int n, m, head[maxn], tot;
long long ans;
long long f[maxn][1 << maxn];
struct Edge {
int y, next;
Edge() {}
Edge(int _y, int _next) : y(_y), next(_next) {}
}e[maxn * maxn];
vector<int>num[maxn];
inline void connect(int x, int y) {
e[++tot] = Edge(y, head[x]);
head[x] = tot;
}
inline int call(int s, int i) {
return (s >> i) & 1;
}
inline int count(int s) {
int res = 0;
for (int i = 0; i < n; ++i) {
if (call(s, i)) ++res;
}
return res;
}
inline int lowbit(int x) { return x & (-x); }
int main() {
n = read(); m = read();
for (int i = 1; i <= m; ++i) {
int x = read(), y = read();
--x; --y;
connect(x, y); connect(y, x);
}
for (int s = 0; s < (1 << n); ++s) {
num[count(s)].push_back(s);
}
for (int x = 0; x < n; ++x)
f[x][1 << x] = 1;
for (int cnt = 1; cnt <= n; ++cnt) {
for (int idx = 0; idx < (int)num[cnt].size(); ++idx) {
int s = num[cnt][idx];
for (int x = 0; x < n; ++x) {
if (!call(s, x)) continue;
for (int i = head[x]; i; i = e[i].next) {
int y = e[i].y;
if (lowbit(s) > (1 << y)) continue;
if (lowbit(s) == (1 << y)) {
ans += f[x][s];
}
if (!call(s, y))
f[y][s | (1 << y)] += f[x][s];
}
}
}
}
ans -= m;
printf("%lld\n", ans / 2);
return 0;
}