Solution - Codeforces 1394B Boboniu Walks on Graph
考虑先分析最后的图会长成什么样。
因为每个点都只会连出一条有向边,且最后还能走回自己。
所以可以知道的是图会有许多个环来组成,且每个环都无交。
但是这个判定条件明显不是很优秀,考虑继续转化。
考虑到对于一个有向环,每个点的出度和入度都需要为 \(1\)。
那么出度为 \(1\) 题目本身就要求满足了,所以实际上只需要使得每个点的入度都为 \(1\) 就可以了。
那么继续转化,考虑反面,那就是要求不存在点的入度 \(> 1\)。
(为什么不考虑入度 \(= 0\) 的?因为边数总共为 \(n\),有入度为 \(0\) 就一定有入度 \(> 1\) 的。)
那么就比较好做了,处理出一个点 \(v\) 对应的入边 \((u, v, w)\) 在 \(u\) 处对应的出边的排名和 \(u\) 的入度,表示为二元组 \((rk_{(u, v, w)}, deg_u)\)。
那么对于两条入边 \((u, v, w)\) 和 \((u', v, w')\),就要求这两条边对应的二元组 \((rk_{(u, v, w)}, deg_u), (rk_{(u', v, w')}, deg_{u'})\) 不能同时被选中。
那么因为总共的二元组个数不超过 \(1 + 2 + \cdots + 9 = 45\) 个,可以直接二进制压缩。
用 \(f_u\) 表示选了 \(u\) 就不能选的二元组的集合,可以通过每一个点 \(v\) 的情况推出。
那么就可以直接爆搜,每次选出一个出度对应的二元组后判断是否会产生冲突即可。
时间复杂度 \(\mathcal{O}(nk\log k + k!)\)。
#include<bits/stdc++.h>
using ll = long long;
constexpr int maxn = 2e5 + 10;
constexpr int pre[10] = {0, 0, 1, 3, 6, 10, 15, 21, 28, 36};
inline int id(int x, int y) {
return x + pre[y];
}
int n, m, k;
std::vector<std::pair<int, int> > to[maxn];
int c[maxn][45];
ll f[maxn], g[45];
int ans;
void dfs(int d, ll mask) {
if (d > k) {
return ans++, void();
}
for (int i = 0; i < d; i++) {
if (g[id(i, d)] & (mask | (1ll << id(i, d)))) continue;
dfs(d + 1, mask | (1ll << id(i, d)));
}
}
int main() {
scanf("%d%d%d", &n, &m, &k);
for (int x, y, z; m--; ) {
scanf("%d%d%d", &x, &y, &z);
to[x].emplace_back(z, y);
}
for (int i = 1; i <= n; i++) {
std::sort(to[i].begin(), to[i].end());
for (int j = 0; j < to[i].size(); j++) {
int v = to[i][j].second, w = id(j, to[i].size());
c[v][w]++;
f[v] |= 1ll << w;
}
}
for (int i = 1; i <= n; i++) {
for (int w = 0; w < 45; w++) {
if (f[i] >> w & 1ll) {
g[w] |= f[i] ^ ((ll)(c[i][w] == 1) << w);
}
}
}
dfs(1, 0);
printf("%d\n", ans);
return 0;
}