题解:P17410 【MX-X31-T6】「FAOI-R14」数据生成器

太困难了,非常好的题。

设 \(row_i=\sum_jh_{i,j}\),\(col_j=\sum_ih_{i,j}\),显然要有 \(a_i\leq row_i\),\(b_j\leq col_j\)。将 \(a_i=row_i\) 的行称为紧行,\(a_i<row_i\) 的行成为松行,类似定义紧列和松列。

建图刻画对 \(h\) 的修改:给每行和每列建一个点,对于每个 \(c_{i,j}=2\),若 \(h_{i,j}=0\) 则连 \(i\to j\) 表示 \(d_{i,j}=1\),若 \(h_{i,j}=1\) 则连 \(i\gets j\) 表示 \(d_{i,j}=0\)。

考虑执行一条有向路径上的所有修改,观察到中间的点对应的 \(1\) 的个数不变,只需要考虑端点的变化。直接分讨:

  • 若起点和终点都是行点,则要求终点是松行,\(1\) 的总个数不变。
  • 若起点和终点都是行点,则要求起点是松列,\(1\) 的总个数不变。
  • 若起点是列点,终点是行点,则要求起点是松列、终点是松行,\(1\) 的总个数减 \(1\)。
  • 若起点是行点,终点是列点,则没有额外要求,\(1\) 的总个数加 \(1\)。

显然前面三种情况都会导致 \(h\) 不是唯一最优解,因此可以得到若干必要条件:

  • 建出来的图是一个 DAG。否则取一个环上的边修改,\(1\) 的个数不变。
  • 不存在从任意行到松行的路径。
  • 不存在从松列到任意列的路径。
  • 不存在从松列到松行的路径。

值得指出的是,我们只选一条简单路径翻转一次,所以松行对应的 \(a_i\) 取 \([0,row_i-1]\) 内的任意整数都是合法的,松列同理。

猜测上面的条件也是充分的。

充分性证明

反证法,假设存在另一个解 \(h'\)。

对于每个 \(c_{i,j}=2\),若 \(h_{i,j}=0\) 且 \(h'_{i,j}=1\) 则连 \(i\to j\),若 \(h_{i,j}=1\) 且 \(h'_{i,j}=0\) 则连 \(i\gets j\)。这样建出来的图是原图的一个子图。

在新图中定义 \(f(u)=\operatorname{deg}_{\text{out}}(u)-\operatorname{deg}_{\text{in}}(u)\),那么对于行点,\(f(u)=\sum_jh'_{i,j}-row_i\),对于列点,\(f(u)=col_j-\sum_ih'_{i,j}\)。因此紧行满足 \(f(u)\geq 0\),紧列满足 \(f(u)\leq 0\)。观察到必要条件中后面的三个条件都满足起点 \(f(s)>0\) 或者终点 \(f(t)<0\)。考虑把新图分解成若干条 \(f(s)>0\) 且 \(f(t)<0\) 的路径:建立源点 \(S\) 和汇点 \(T\),对于每个 \(f(u)>0\) 从 \(S\) 向 \(u\) 连 \(f(u)\) 条边,对于每个 \(f(u)<0\) 从 \(u\) 向 \(T\) 连 \(-f(u)\) 条边,这样得到的新的 DAG 出入度相等,不断分解出一条从 \(S\) 到 \(T\) 的路径即可。现在考察每条分解出来的路径,发现只能是从行到列的路径,因此 \(h'\) 比 \(h\) 劣。矛盾。\(\Box\)

尝试对这个充要条件计数。

下面设 \(R\) 是所有行点构成的集合,\(C\) 是所有列点构成的集合。

每个 \(h_{i,j}\) 都对应 DAG 上的一条边,从 \(c_{i,j}\) 的取值集合中排除掉 \(1-h_{i,j}\) 后,若 \(c_{i,j}\) 只能取 \(2\),则这条边必选;若 \(c_{i,j}\) 可以取 \(2\) 和 \(h_{i,j}\),则这条边可选;若 \(c_{i,j}\) 只能取 \(h_{i,j}\),则这条边不存在。

先尝试去掉行的限制:设 \(f_S\) 表示考虑 \(S\) 上的诱导子图, 所有行都是紧行的方案数。若存在紧的零入度点,则去掉紧的零入度点不影响合法性,容斥钦定 \(T\) 里的点是紧的零入度点:

\[f_S\gets \sum_{\varnothing\neq T\subseteq S}(-1)^{|T|+1}\operatorname{cnt}(S,T)f_{S\setminus T} \]

其中 \(\operatorname{cnt}(S,T)\) 表示在 \(S\) 内钦定 \(T\) 里的点是零入度点后,从 \(T\) 连出去的边的方案数。若存在 \(S\) 的诱导子图中存在连向 \(T\) 的必选边,则 \(\operatorname{cnt}(S,T)=0\),否则 \(\operatorname{cnt}(S,T)=2^{\operatorname{to}(T,S\setminus T)}\),\(\operatorname{to}(X,Y)\) 表示起点在 \(X\) 内,终点在 \(Y\) 内的可选边数量。

若不存在紧的零入度点,则零入度点都是松列,而不能包含从松列到列的路径,因此实际上所有列都是松列,零入度点恰好是所有列点。但此时要求紧行入度 \(>0\),对这个条件容斥,钦定 \(T\) 里的紧行入度为 \(0\),那么钦定后的零入度点集合就是 \((S\cap C)\cup T\),得到式子:

\[f_S\gets \prod_{j\in S\cap C}col_j\sum_{T\subseteq S\cap R}(-1)^{|T|}\operatorname{cnt}(S,(S\cap C)\cup T) \]

注意要乘上松列的权值。

现在允许存在松行,令 \(g_S\) 表示考虑 \(U=R\cup S\) 上的诱导子图的方案数,其中 \(S\subseteq C\)。若存在入度为 \(0\) 的紧列点,则去掉这些点不影响合法性,容斥钦定 \(T\) 里的点是入度为 \(0\) 的紧列点:

\[g_S\gets\sum_{\varnothing\neq T\subseteq S}(-1)^{|T|+1}\operatorname{cnt}(U,T)g_{S\setminus T} \]

若不存在入度为 \(0\) 的紧列点,则零入度点只能是行点或者松列点。而条件限制不存在行或者松列能到达松行的路径,因此松行一定都是零入度点,可以枚举松行对应的点集 \(A\),贡献 \(\prod\limits_{i\in A}row_i\) 的权值,去掉这些松行点后剩下的部分就是 \(f\) 了。但是我们还需要保证紧列入度 \(>0\),对这个条件容斥,钦定 \(B\) 里的点是入度为 \(0\) 的紧列点,那么钦定后的零入度点集合为 \(A\cup B\),得到式子:

\[g_S\gets\sum_{A\subseteq R}\sum_{B\subseteq S}(-1)^{|B|}\operatorname{cnt}(U,A\cup B)f_{U\setminus(A\cup B)}\prod\limits_{i\in A}row_i \]

注意到 \(A,B\) 不交,所以实际上可以直接枚举 \(A\cup B=T\subseteq U\):

\[g_S\gets\sum_{T\subseteq U}(-1)^{|T\cap S|}\operatorname{cnt}(U,T)f_{U\setminus T}\prod\limits_{i\in T\cap R}row_i \]

最终答案就是 \(g_C\)。

固定 \(S\) 后可以从小到大枚举 \(T\) 计算 \(\operatorname{to}(T,S\setminus T)\):任取 \(u\in T\),设 \(T'=T\setminus\{u\}\),从 \(\operatorname{to}(T',S\setminus T')\) 中加上从 \(u\) 连向 \(S\setminus T\) 的可选边数,再减去从 \(T'\) 连向 \(u\) 的可选边数即可。

时间复杂度为 \(\mathcal{O}(3^{n+m})\)。

主要代码
int n, m;
int row[MAXN], col[MAXN], to[1 << 16];
int in[MAXN << 1], out[MAXN << 1], in2[1 << 16];
string S[MAXN];
bool h[MAXN][MAXN];
mint pw2[MAXN * MAXN + 1], pr[1 << MAXN], pc[1 << MAXN];
mint f[1 << 16], g[1 << 8], cnt[1 << 16];

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	
	cin >> n >> m;
	for (int i = 0; i < n; ++i) cin >> S[i];
	for (int i = 0; i < n; ++i) {
		string s;
		cin >> s;
		for (int j = 0; j < m; ++j) {
			h[i][j] = s[j] - '0';
			row[i] += h[i][j];
			col[j] += h[i][j];
		}
	}

	for (int i = 0; i < n; ++i) {
		for (int j = 0; j < m; ++j) {
			int x = S[i][j] - '0', u = !h[i][j] ? i : j + n, v = !h[i][j] ? j + n : i;
			if ((x >> 2 & 1) && (x >> h[i][j] & 1)) {
				in[v] |= 1 << u;
				out[u] |= 1 << v;
			} else if (x >> 2 & 1) {
				in2[1 << v] |= 1 << u;
			}
		}
	}

	pw2[0] = 1;
	for (int i = 1; i <= n * m; ++i) pw2[i] = pw2[i - 1] * 2;

	pr[0] = 1;
	for (int s = 1; s < 1 << n; ++s) {
		int i = __builtin_ctz(s);
		pr[s] = pr[s ^ (1 << i)] * row[i];
	}
	pc[0] = 1;
	for (int s = 1; s < 1 << m; ++s) {
		int i = __builtin_ctz(s);
		pc[s] = pc[s ^ (1 << i)] * col[i];
	}

	for (int s = 1; s < (1 << n + m); ++s) in2[s] = in2[s ^ lowbit(s)] | in2[lowbit(s)];

	f[0] = 1;
	int R = (1 << n) - 1, C = ((1 << n + m) - 1) ^ R;
	for (int s = 1; s < (1 << n + m); ++s) {
		for (int t = 0;; t = t - s & s) {
			if (!t) {
				to[t] = 0;
				cnt[t] = 1;
			} else {
				int u = __builtin_ctz(t);
				to[t] = to[t ^ (1 << u)] + __builtin_popcount(out[u] & (s ^ t)) - __builtin_popcount(in[u] & (t ^ (1 << u)));
				cnt[t] = in2[t] & s ? 0 : pw2[to[t]];
			}
			if (t == s) break;
		}

		for (int t = s; t; t = (t - 1) & s) f[s] += (__builtin_popcount(t) & 1 ? 1 : -1) * cnt[t] * f[s ^ t];
		for (int t = s & R;; t = (t - 1) & s & R) {
			f[s] += (__builtin_popcount(t) & 1 ? -1 : 1) * pc[(s & C) >> n] * cnt[(s & C) | t];
			if (!t) break;
		}

		if ((s & R) != R) continue;
		int sp = s ^ R;
		for (int t = sp; t; t = (t - 1) & sp) g[sp >> n] += (__builtin_popcount(t) & 1 ? 1 : -1) * cnt[t] * g[(sp ^ t) >> n];
		for (int t = s;; t = (t - 1) & s) {
			g[sp >> n] += (__builtin_popcount(t & sp) & 1 ? -1 : 1) * pr[t & R] * cnt[t] * f[s ^ t];
			if (!t) break;
		}
	}

	cout << g[(1 << m) - 1];
	return 0;
}
posted @ 2026-10-01 21:55  P2441M  阅读(13)  评论(0)    收藏  举报