CF11D A Simple Task
CF11D A Simple Task - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
数据范围容易让人想到状压 dp,状压对象为点集。
现有一条点集为 \(S\) 的路径。目标是路径成环,所以转移时需要路径的开头和结尾两个信息。
可以想到:\(f(S, u, v)\) 表示从 \(u\) 到 \(v\) 经过的点集为 \(S\) 的路径数量。
但是这样一来,每个长度为 \(L\) 的环会被统计 \(2L\) 次(每个点为起点,顺时针或逆时针走向)。为了避免重复,在每次统计答案 \(f(S, u, u)\) 的时候我们需要除以 \(L\),即 \(\operatorname{popcnt}(S)\)。这里就会涉及到浮点数的计算,所以 \(f\) 必须用浮点类型存,但是浮点类型无法在 250MB 内存储一个大小为 \(n^2 \times 2^n\) 的数组。
怎么办?考虑优化状态:\(f(S, u)\) 表示从 \(\operatorname{lowbit}(S)\) 到 \(u\) 经过的点集为 \(S\) 的路径数量。
这样一来,每个长度为 \(L\) 的环只会被该环中编号最小的节点统计 \(2\) 次(顺时针或逆时针走向)。\(2\) 是一个常数,因此我们直接统计 \(f\),最后除以 \(2\) 即可。这样以来 \(f\) 被优化成了一个 \(n \times 2^n\) 的整数数组,可以通过。
换句话说,我们只统计满足这个条件的路径:开头是 \(s\),那么之后经过的点都比 \(s\) 编号要大。
转移是显然的:\(f(S \cup \{v\}, v) \gets f(S \cup \{v\}, v) + f(S, u)[(u, v) \in G]\)。
最后,我们发现长度为 \(2\) 的“环”也会被统计。注意,长度为 \(2\) 的环只会被统计 \(1\) 次而不是 \(2\) 次,因为顺时针和逆时针走向实际上是一样的。
这里有两种方案:
- 只统计 \(\operatorname{popcnt}(S) \ge 3\) 的 \(f(S, \operatorname{lowbit}(S))\),设和为 \(sum\),则答案为 \(\dfrac{sum}{2}\);
- 都统计,设和为 \(sum\),则答案为 \(\dfrac{sum - m}{2}\)。
第二种实现比较简单。
时间复杂度 \(\mathcal{O}(n^2 \times 2^n)\)。
/*
* @Author: crab-in-the-northeast
* @Date: 2022-10-19 19:45:25
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2022-10-19 20:59:00
*/
#include <bits/stdc++.h>
#define int long long
inline int read() {
int x = 0;
bool flag = true;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
flag = false;
ch = getchar();
}
while (isdigit(ch)) {
x = (x << 1) + (x << 3) + ch - '0';
ch = getchar();
}
if(flag)
return x;
return ~(x - 1);
}
const int maxn = 20;
int f[1LL << maxn][maxn];
bool a[maxn][maxn];
signed main() {
int n = read(), m = read();
for (int i = 1; i <= m; ++i) {
int u = read() - 1, v = read() - 1;
a[u][v] = a[v][u] = true;
}
int N = (1LL << n);
for (int u = 0; u < n; ++u)
f[1LL << u][u] = 1;
int ans = 0;
for (int S = 0; S < N; ++S) {
int s = (S & (-S));
for (int u = 0; u < n; ++u) {
for (int v = 0; v < n; ++v) if (a[u][v]) {
if ((1LL << v) < s)
continue;
if (S & (1LL << v)) {
if ((1LL << v) == s)
ans += f[S][u];
} else
f[S | (1LL << v)][v] += f[S][u];
}
}
}
printf("%lld\n", (ans - m) >> 1LL);
return 0;
}