题解: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\) 里的点是紧的零入度点:
其中 \(\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\),得到式子:
注意要乘上松列的权值。
现在允许存在松行,令 \(g_S\) 表示考虑 \(U=R\cup S\) 上的诱导子图的方案数,其中 \(S\subseteq C\)。若存在入度为 \(0\) 的紧列点,则去掉这些点不影响合法性,容斥钦定 \(T\) 里的点是入度为 \(0\) 的紧列点:
若不存在入度为 \(0\) 的紧列点,则零入度点只能是行点或者松列点。而条件限制不存在行或者松列能到达松行的路径,因此松行一定都是零入度点,可以枚举松行对应的点集 \(A\),贡献 \(\prod\limits_{i\in A}row_i\) 的权值,去掉这些松行点后剩下的部分就是 \(f\) 了。但是我们还需要保证紧列入度 \(>0\),对这个条件容斥,钦定 \(B\) 里的点是入度为 \(0\) 的紧列点,那么钦定后的零入度点集合为 \(A\cup B\),得到式子:
注意到 \(A,B\) 不交,所以实际上可以直接枚举 \(A\cup B=T\subseteq U\):
最终答案就是 \(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;
}

浙公网安备 33010602011771号