P8189 [USACO22FEB] Redistributing Gifts G
模拟赛题,挺简单的。
知周所众,\(n\leq 18\) 大概率是状压(雾)。
首先发现实际上题目的很大一部分都没有用,实际上是在求:
求排列 \(b\),每个位置 \(i\),限制只能放 \(L_i\) 中的数。设 \(F(S)\) 表示对于下标集合 \(S\),只考虑 \(i \in S\) 的位置,\(i\in S, b_i\in S,b_i\in L_i\) 的方案数。则答案为 \(F(S) \cdot F(\complement_{[1,n]\cap \mathbb{Z}} S )\)。
但是直接算 \(F\) 有点难,怎么办,注意到合并下标集合 \(S\) 和 \(\{x\}\) 的答案时,我们可以做两件事情:
-
可以让 \(b_x = x\),这样一定合法;
-
在第一种方案的基础上,找到一个数 \(y\in S,b_y\in L_x,x\in L_y\),可以进行一次“交换”操作,交换 \(b_x\) 和 \(b_y\)。这样也一定是合法的。
于是可以设计一种状态 \(f_{S,x}\) 表示只考虑下标 \(i\in S\) 的位置,钦定下标最小位置上 \(b_i=x\) 且这个位置不需要考虑 \(x \in L_i\) 的限制的方案数。
考虑转移,计算 \(S\) 时,枚举要把哪个数 \(x\) 放在下标最小的位置上,然后让 \(x\) 和最低位进行一次交换即可。
复杂度 \(O(2^n n^2)\)。
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr inline int bit(const unsigned &x) {
return 1 << x;
}
constexpr inline int dig(const int &x, const unsigned &k) {
return (x >> k) & 1;
}
int a[20];
int pos[20];
int ac[20];
ll f[bit(20)][20];
ll g[bit(20)];
int n, q;
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
cin >> a[j];
pos[a[j]] = j;
}
for(int j = 1; j <= n; j++) {
if(pos[j] <= pos[i])
ac[i] |= bit(j - 1);
}
}
g[0] = 1;
for(int i = 1; i <= n; i++) {
g[bit(i - 1)] = f[bit(i - 1)][i] = 1;
}
for(int S = 1; S < bit(n); S++) {
if(__builtin_popcount(S) <= 1) continue;
int cur = __builtin_ctz(S) + 1;
ll del = g[S ^ bit(cur - 1)];
f[S][cur] += del;
g[S] += del;
for(int i = cur + 1; i <= n; i++) {
if(!(S & bit(i - 1))) continue;
int can = ac[i] & (S ^ bit(i - 1));
for(int j = 1; j <= n; j++) {
if(!dig(can, j - 1)) continue;
ll del = f[S ^ bit(i - 1)][j];
f[S][i] += del;
if(dig(ac[cur], i - 1)) g[S] += del;
}
}
}
cin >> q;
for(int i = 1; i <= q; i++) {
char s[20];
cin >> s;
int S = 0;
for(int j = 1; j <= n; j++) {
if(s[j - 1] == 'H') S |= bit(j - 1);
}
cout << (g[S] * g[(bit(n) - 1) ^ S]) << '\n';
}
}