CF908H New Year and Boolean Bridges

发布时间 2023-11-01 17:10:21作者: Ender_32k

这说明你那破子集卷积不是万能的。

显然题目要求的图 \(G'\) 是弱联通的,考虑给出的图 \(G\) 中两个点 \(i,j\) 之间 \(G_{i,j}\) 的条件转化为:

  • \(G_{i,j}=\mathtt A\),说明 \(i\) 能到 \(j\)\(j\) 能到 \(i\),则 \(i,j\) \(G'\) 的同一个强连通分量中。
  • \(G_{i,j}=\mathtt O\),说明 \(i\) 能到 \(j\) 或者 \(j\) 能到 \(i\),由于 \(G_{i,j}=\mathtt A/\mathtt X\) 是严格强于这个限制的,所以可以转换为 \(G'\) 中任意两点 \(u,v\) 之间至少存在一条路径\(u\)\(v\) 或从 \(v\)\(u\)
  • \(G_{i,j}=\mathtt X\),说明只能从 \(i\)\(j\) 或从 \(j\)\(i\),这要求 \(i,j\) 不在 \(G'\) 的同一个强连通分量中。

考虑仅由 \(\mathtt A\) 边构成的极大导出子图 \(G_\mathtt A\subseteq G\),显然图 \(G_\mathtt A\) 中同一个弱连通块内的任意两点 \(u,v\) 在图 \(G'\) 中强连通,于是如果 \(G_{u,v}=\mathtt X\) 就矛盾了,先判掉这种情况,答案为 \(-1\)

否则的话必然有解,存在一种构造:考虑 \(G_{\mathtt A}\) 中的某个大小 \(\ge 2\) 的弱联通块,将内部的点拿出来,在 \(G'\) 中单独组成一个环;然后 \(G'\) 中就变成了若干个环加上若干孤立点,把它们串成一条链即可满足所有 \(\mathtt O\) 边的性质。
于是每个环(\(G_{\mathtt A}\) 中的弱连通块) \(C\) 内部有 \(|V_C|\) 条边,然后会连出去一条边;每个孤立点连出去一条边;最后有一个部分不用连出去边。设环集合为 \(\operatorname{cyc}\),那么总边数就是 \(\sum\limits_{C\in \operatorname {cyc}}(|V_C|+1)+(n-\sum|V_C|)-1=n-1+|\operatorname{cyc}|\)

但是我们发现图 \(G_{\mathtt A}\) 中的两个大小 \(\ge 2\) 的弱联通块是可以合并的,因为两个弱联通块内的点在 \(G'\) 中可以属于同一个强连通分量,那么 \(|\operatorname{cyc}|\) 就会减少 \(1\),边数就会变少。所以我们希望能够合并尽可能多的弱连通块,使得最后弱联通块总数最少。注意到 \(G_{u,v}=\mathtt X\) 的限制相当于 \(u\) 所在的弱联通块不能和 \(v\) 所在的弱连通块合并。

由于我们考虑的弱联通块大小 \(\ge 2\),所以弱联通块数 \(m\le\frac{n}{2}\le 23\)。考虑状压 dp,\(f_{i,S}\) 表示能否将弱联通块集合 \(S\) 全部合并成 \(i\) 个块。那么 \(\text{ans}=n-1+\min\limits_{f_{i,U}=1}i\)\(U\) 为弱联通块全集。

首先 \(f_{1,S}\) 是易求的,预处理 \(w_i\) 表示第 \(i\) 个弱联通块不能合并的弱联通块集合即可。然后考虑判定性问题转计数问题,\(f_{i,S}\) 表示合并方案数,对于 \(i\ge 2\)

\[f_{i,S}=\sum\limits_{S_1\cup S_2=S,S_1\cap S_2=\varnothing}f_{i-1,S_1}f_{1,S_2} \]

不难发现这就是个子集卷积,但是复杂度 \(O(m^32^m)\),拿头跑都过不了。但是由于我们不关心 \(f_{i,S}\) 的具体值,对同一个方案可以算重,而且若 \(f_{i,S}\neq 0\) 必然有 \(f_{i,T}\neq 0,T\subseteq S\)。所以我们就可以直接把 \(S_1\cap S_2=\varnothing\) 的限制去掉了。最后相当于一个或卷积,复杂度 \(O(m^22^m)\),好像已经可以过了,但是能做到更优。

考虑到进行 FWT 或变换(高维前缀和)后 \([x^S]\operatorname{FWT}(F_{i-1})\)\([x^S]\operatorname{FWT}(F_{i})\) 的转移系数固定为 \([x^S]\operatorname{FWT}(F_1)\),于是我们可以预先求出 \(f_{1}\) 的集合幂级数 \(F_1\) 后进行 FWT,于是 \([x^S]\text{FWT}(F_{i})=([x^S]\operatorname{FWT}(F_1))^i\)

最后的问题就是如何求出 \([x^U]F_i\)。根据高维前缀和的性质有:

\[[x^S]\operatorname{FWT}(F_i)=\sum\limits_{T\subseteq S}[x^T]F_i \]

子集反演得:

\[[x^S]F_i=\sum\limits_{T\subseteq S}(-1)^{|S|-|T|}[x^T]\operatorname{FWT}(F_i) \]

所以:

\[\begin{aligned}[x^U]F_i&=\sum\limits_{T}(-1)^{m-|T|}[x^T]\operatorname{FWT}(F_i)\\&=\sum\limits_{T}(-1)^{m-|T|}([x^T]\operatorname{FWT}(F_1))^i\end{aligned} \]

可以 \(O(2^m)\) 求出,而 \(i\) 的上界为 \(m\),所以复杂度就是 \(O(m2^m)\)

// Problem: New Year and Boolean Bridges
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF908H
// Memory Limit: 500 MB
// Time Limit: 5000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
#define pb emplace_back
#define mt make_tuple
#define mp make_pair
#define fi first
#define se second

using namespace std;
typedef long long ll;
typedef pair<int, int> pi;
typedef tuple<int, int, int> tu;
bool Mbe;

const int N = 50;
const int T = (1 << 23) + 100;

int n, m, S, fa[N], sz[N], w[N], id[N];
ll op[T], f[T], c[T];
char s[N][N];

int gf(int x) { return x == fa[x] ? x : fa[x] = gf(fa[x]); }
void mrg(int x, int y) {
	if ((x = gf(x)) == (y = gf(y))) return;
	fa[x] = y, sz[y] += sz[x];
}

void FWT(ll *f) {
	for (int o = 2, k = 1; o <= S; o <<= 1, k <<= 1)
		for (int i = 0; i < S; i += o)
			for (int j = 0; j < k; j++)
				f[i + j + k] += f[i + j];
}

void solve() {
	cin >> n;
	for (int i = 1; i <= n; i++) sz[fa[i] = i] = 1;
	for (int i = 1; i <= n; i++) {
		cin >> (s[i] + 1);
		for (int j = 1; j < i; j++) 
			if (s[i][j] == 'A') mrg(i, j);
	}
	for (int i = 1; i <= n; i++) {
		if (gf(i) == i && sz[i] >= 2) id[i] = ++m;
		id[i] = id[gf(i)];
	}
	if (!m) return cout << n - 1 << '\n', void();
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j < i; j++) {
			if (s[i][j] == 'X') {
				if (id[i] && id[i] == id[j]) return cout << -1 << '\n', void();
				else if (id[i] && id[j]) w[id[i]] |= (1 << id[j] - 1), w[id[j]] |= (1 << id[i] - 1);
			}
		}
	}
	S = (1 << m), op[0] = 1;
	for (int i = 1; i < S; i++) {
		int j = __lg(i & -i);
		op[i] = op[i ^ (1 << j)] & (!(w[j + 1] & (i ^ (1 << j))));
	} 
	if (op[S - 1]) return cout << n << '\n', void();
	FWT(op), memcpy(f, op, sizeof(ll) * (S + 5));
	for (int i = 0; i < S; i++) c[i] = (m - __builtin_popcount(i) & 1) ? -1 : 1;
	for (int r = 2; ; r++) {
		ll res = 0;
		for (int i = 0; i < S; i++) f[i] *= op[i], res += f[i] * c[i];
		if (res) return cout << n - 1 + r << '\n', void();
	}
}

bool Med;
int main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cerr << (&Mbe - &Med) / 1048576.0 << " MB\n";
	#ifdef FILE
		freopen("1.in", "r", stdin);
		freopen("1.out", "w", stdout);
	#endif
	int T = 1;
	// cin >> T;
	while (T--) solve();
	cerr << (int)(1e3 * clock() / CLOCKS_PER_SEC) << " ms\n";
	return 0;
}