专栏文章
题解:P11714 [清华集训 2014] 主旋律
P11714题解参与者 6已保存评论 10
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 10 条
- 当前快照
- 1 份
- 快照标识符
- @miq8erj1
- 此快照首次捕获于
- 2025/12/04 00:38 3 个月前
- 此快照最后确认于
- 2025/12/04 00:38 3 个月前
题目描述
给定一个 个点的有向图,问这个图有多少个强连通子图。保证 。
前置问题
一个有向图有 个点,求它的 DAG 子图数量。。
解:令 表示点集 有多少个 DAG 子图。枚举这个 DAG 中入度为 0 的部分,然后递归下去。由于不能一次枚举完所有入度为 0 的点,我们对着这些点做容斥。对于一张合法的 DAG 来说,假如它的零度点集合为 ,则我们会计算它 次,由二项式定理我们知道这个系数恰好为 ,即它是 DAG 时系数为 ,不是 DAG 时系数为 。
solution
一个非强连通的子图,缩点后是一个至少两个点的 DAG(这个 DAG 可以不连通)。
对于点集 ,考虑强连通子图 所有图 非强连通子图。
令只考虑点集 的强连通子图的数量为 ,枚举 ,使 是一个强连通分量, 就随意,而且强制规定 在缩点图的入度为零。
- 表示点集为 导出的所有图的数量。
- 表示 。注意到 ,它的复杂度顶满是 。
这是错误的。考虑图为 时会算至少两次。
考虑问题在哪里呢?枚举 的时候,我们没有保证 一定是所有的入度为 的点。我们对着 容斥:
- 将 划分为若干个强连通分量,奇数个的贡献是 ,偶数个的贡献是 ,这个系数和前置问题中的系数是一样的。
- 令 表示所有划分方案的和。
将 换成 就行了。
怎么算 ?
- 枚举一个 ,然后划分成 。注意 不能和 取等,因为整个 都是强连通分量不是我们需要减去的东西(非强连通子图)。
- 为了避免判重等问题,我们钦定一个 ,然后枚举 的 。
- 转移顺序为:先计算 但是不用加上 ,然后计算 ,最后再将 加到 ,保证 的转移合法。
复杂度 。
以上复杂度无法通过。事实上只需要优化 这个瓶颈的计算。提前计算 为一个 大小的数,第 位上是 表示存在 。计算 枚举 ,然后计算 与 的按位与,再求这个结果的
__builtin_popcount,即可将复杂度削减一个 。以上复杂度无法通过另一道依赖本问题的题目,这里就不点明是什么题目了。事实上只需要优化 这个瓶颈的计算。预先处理两个数组 ,然后将 映射为 ,形成一个 的数组,计算时只需要随意选定 ,拿出 的结果和 相加即可,复杂度 。
code
代码中
CPPb 对应 数组,pw2[h[S]] 对应 。#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
using LL = long long;
template <unsigned umod>
struct modint {/*{{{*/
static constexpr int mod = umod;
unsigned v;
modint() = default;
template <class T, enable_if_t<is_integral<T>::value, int> = 0>
modint(const T& y) : v((unsigned)(y % mod + (is_signed<T>() && y < 0 ? mod : 0))) {}
modint& operator+=(const modint& rhs) { v += rhs.v; if (v >= umod) v -= umod; return *this; }
modint& operator-=(const modint& rhs) { v -= rhs.v; if (v >= umod) v += umod; return *this; }
modint& operator*=(const modint& rhs) { v = (unsigned)(1ull * v * rhs.v % umod); return *this; }
modint& operator/=(const modint& rhs) { assert(rhs.v); return *this *= qpow(rhs, mod - 2); }
friend modint operator+(modint lhs, const modint& rhs) { return lhs += rhs; }
friend modint operator-(modint lhs, const modint& rhs) { return lhs -= rhs; }
friend modint operator*(modint lhs, const modint& rhs) { return lhs *= rhs; }
friend modint operator/(modint lhs, const modint& rhs) { return lhs /= rhs; }
template <class T> friend modint qpow(modint a, T b) {
modint r = 1;
for (assert(b >= 0); b; b >>= 1, a *= a) if (b & 1) r *= a;
return r;
}
friend int raw(const modint& self) { return self.v; }
friend ostream& operator<<(ostream& os, const modint& self) { return os << raw(self); }
explicit operator bool() const { return v != 0; }
modint operator-() const { return modint(0) - *this; }
bool operator==(const modint& rhs) const { return v == rhs.v; }
bool operator!=(const modint& rhs) const { return v != rhs.v; }
};/*}}}*/
using mint = modint<(int)1e9 + 7>;
template <class T>
constexpr auto lowbit(T x) { return x & -x; }
constexpr int pw3[] = {1, 3, 9, 27, 81, 243, 729, 2187, 6561, 19683, 59049, 177147, 531441, 1594323, 4782969, 14348907, 43046721, 129140163, 387420489, 1162261467};
constexpr int inv2 = (mint::mod + 1) / 2;
struct subset {/*{{{*/
int s;
struct iterator {
int s, t;
constexpr bool operator!=(const iterator& rhs) const { return t != rhs.t; }
constexpr iterator& operator++() { t = t ? (t - 1) & s : -1; return *this; }
constexpr int operator*() const { return t; }
};
constexpr auto begin() const { return iterator{s, s}; }
constexpr auto end() const { return iterator{s, -1}; }
};/*}}}*/
struct eachbit {/*{{{*/
int s;
struct iterator {
int s;
constexpr bool operator!=(const iterator& rhs) const { return s != rhs.s; }
constexpr iterator& operator++() { s -= lowbit(s); return *this; }
constexpr int operator*() const { return __builtin_ctz(s); }
};
constexpr auto begin() const { return iterator{s}; }
constexpr auto end() const { return iterator{0}; }
};/*}}}*/
mint pw2[100010], ipw2[100010];
vector<mint> solve(int n, int g[]) {
vector<mint> f(1 << n), b(1 << n);
vector<int> h(1 << n), w(pw3[n]), ot1(1 << n), ot2(1 << n);
for (int i = 0; i < n; i++) {
for (int j : eachbit{g[i]}) h[1 << i | 1 << j] += 1;
}
for (int i = 0; i < n; i++) {
for (int S = 0; S < 1 << n; S++) if (S >> i & 1) h[S] += h[S ^ (1 << i)];
}
for (int S = 1; S < 1 << n; S++) {
int i = __builtin_ctz(S);
ot1[S] = ot1[S ^ lowbit(S)] + pw3[i];
ot2[S] = ot2[S ^ lowbit(S)] + 2 * pw3[i];
}
for (int S = 1; S < 1 << n; S++) {
for (int T : subset{S}) if (T) {
int i = __builtin_ctz(T);
w[ot1[T] + ot2[S ^ T]] = w[ot1[T ^ lowbit(T)] + ot2[S ^ T]] + __builtin_popcount(g[i] & (S ^ T));
}
}
for (int S = 1; S < 1 << n; S++) {
f[S] = pw2[h[S]];
for (int T : subset{S ^ lowbit(S)}) if (T) b[S] -= b[T] * f[S ^ T];
for (int T : subset{S}) if (T) f[S] -= b[T] * pw2[w[ot1[T] + ot2[S ^ T]] + h[S ^ T]];
b[S] += f[S];
}
return f;
}
int main() {
#ifndef LOCAL
cin.tie(nullptr)->sync_with_stdio(false);
#endif
pw2[0] = 1;
for (int i = 1; i <= 1e5; i++) pw2[i] = pw2[i - 1] + pw2[i - 1];
ipw2[0] = 1;
for (int i = 1; i <= 1e5; i++) ipw2[i] = ipw2[i - 1] * inv2;
int n, g[15]{}, m;
cin >> n >> m;
for (int i = 1, u, v; i <= m; i++) cin >> u >> v, g[--u] |= 1 << --v;
cout << solve(n, g)[(1 << n) - 1] << endl;
return 0;
}
相关推荐
评论
共 10 条评论,欢迎与作者交流。
正在加载评论...