hihoCoder #1646 : Rikka with String II(容斥原理)
阿新 • • 發佈:2019-01-08
題意
給你 \(n\) 個 \(01\) 串 \(S\) ,其中有些位置可能為 \(?\) 表示能任意填 \(0/1\) 。問對於所有填法,把所有串插入到 \(Trie\) 的節點數之和(空串看做根節點)。
\(n \le 20, 1 \le |S_i| \le 50\)
題解
直接算顯然不太好算的。
\(Trie\) 的節點數其實就是本質不同的字首個數,可以看做 \(n\) 個串的所有字首的並集的大小。
我們可以套用容斥原理最初的式子。
\[ \left| \bigcup_{i=1}^n A_i \right| = \sum_{k = 1}^{n} (-1)^{k - 1} \sum_{1 \le i_1 < i_2 < \cdots < i_k \le n} |A_{i_1} \cap A_{i_2} \cap \cdots \cap A_{i_k}| \]
這樣的話,我們就可以轉化成對於每個子集的並的交集了,也就是公共字首的個數。
我們設 \(f(S)\) 為 \(S\) 集合內的所有子串對於 所有填的方案 的公共字首的個數。
那麼答案為 \(ans = \sum_{S \subseteq T} (-1)^{|S| - 1} f(S)\)
如何得到呢?由於 \(n\) 很小我們可以暴力列舉集合,然後列舉當前字首的長度,直接計數。
- 如果當前所有的都是 \(?\) 那麼意味著可以任意填 \(0/1\) 。
- 如果存在一種數字,其他都是 \(?\) ,那麼意味著只能填這種數字。
- 如果存在兩種數字,那麼之後都不可能為公共字首了,直接退出即可。
直接實現是 \(O(2^n n |S|)\) 的。可以把狀態集合合併一下優化到 \(O(2^n |S|)\) 。(但是我太懶了)
程式碼
實現的時候不要忘記是所有填的方案。
#include <bits/stdc++.h> #define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl using namespace std; template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; } template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; } inline int read() { int x(0), sgn(1); char ch(getchar()); for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * sgn; } void File() { #ifdef zjp_shadow freopen ("1646.in", "r", stdin); freopen ("1646.out", "w", stdout); #endif } const int N = 21, L = 51, Mod = 998244353; int n, len[1 << N], Pow[N * L]; char str[N][L]; int main () { File(); n = read(); Set(len, 0x3f); int tot = 0; Rep (i, n) { scanf ("%s", str[i] + 1); len[1 << i] = strlen(str[i] + 1); For (j, 1, strlen(str[i] + 1)) if (str[i][j] == '?') ++ tot; } Pow[0] = 1; For (i, 1, tot) Pow[i] = 2ll * Pow[i - 1] % Mod; Rep (i, 1 << n) chkmin(len[i], min(len[i ^ (i & -i)], len[i & -i])); int ans = 0; Rep (i, 1 << n) if (i) { int res = 0, sum = tot, pre = 0; For (j, 1, len[i]) { int flag = 0, now = 0; Rep (k, n) if (i >> k & 1) { if (str[k][j] == '?') ++ now; else flag |= (str[k][j] - '0' + 1); } sum -= now; if (flag == 3) break; if (!flag) ++ pre; res = (res + Pow[pre + sum]) % Mod; } ans = (ans + (__builtin_popcount(i) & 1 ? 1 : -1) * res) % Mod; } ans += Pow[tot]; if (ans < 0) ans += Mod; printf ("%d\n", ans); return 0; }