【組合數學】【P4996】 咕咕咕
Description
小 F 注意到,自己總是在某些情況下會產生歉意。每當他要檢查自己的任務表來決定下一項任務的時候,如果當前他幹了某些事情,但是沒幹另一些事情,那麼他就會產生一定量的歉意——比如,無論他今天看沒看比賽,只要沒有補完月賽的鍋,他都會在選擇任務的時候產生 1 點歉意。小 F 完成所有任務後,他這一天的歉意值等於他每次選擇任務時的歉意之和。
過高的歉意值讓小 F 感到不安。現在,小 F 告訴你他還有 n 項任務,並告訴你在 m 種情況中的一種 \(state_i\) 的情況下,小 F 會產生 \(a_i\) 點歉意。請你幫忙計算一下,小 F 在那一天所有可能的完成所有任務方式的歉意值之和是多少。
由於答案可能很大,你只需要輸出答案對 \(998244353\) 取模即可。
Input
輸入一行兩個整數 \(n,m\),表示有 \(n\) 項任務,在 \(m\) 種情況中下小 F 會產生歉意值。
輸入接下來 \(m\) 行,每行有一個長度為 \(n\) 的 \(0/1\) 串 和 \(state_i\) 一個歉意值 \(a_i\)。\(state_{i,j}\) 為 \(0/1\) 表示第 \(j\) 項任務此時沒做 / 已經做了。
詳情請參考樣例和樣例解釋。
Output
輸出一行一個整數,表示小 F 在那一天所有可能的完成任務方式的歉意值之和對 \(998244353\) 取模的結果。
Hint
\(1~\leq~n~\leq~20\)
\(1~\leq~m~\leq~\min(2^n,10^5)\)
\(1~\leq~a_i~\leq~10^5\)
Solution
考慮狀壓,發現只能列舉子集轉移,複雜度 \(O(3^n)\),顯然過不去
接著發現對答案產生貢獻的狀態並不多,考慮是否可以直接統計這些狀態對答案產生的貢獻。
考慮一個非常顯然的事情,如果狀態 \(A\) 的 \(1\) 的個數與狀態 \(B\) 的 \(1\) 的個數相同,那麼從空集轉到狀態 \(A\) 和 \(B\) 的方案數是相同的,同時從這兩個狀態轉到全集的方案數也是相同的。
根據乘法原理,從空集經過狀態 \(S\)
於是答案即為 \(\sum_{i=1}^{m}~cnt_{state_{i_1}}~\times~cnt{state_{i_0}}~\times~a_i\)。
考慮怎麼求 \(cnt\) 陣列。顯然可以 \(O(3^n)\) 狀壓在本機跑一會然後打表。
考慮正常一點的做法。發現 \(cnt\) 的組合意義即為從幾個數中選出幾個當 \(1\) 然後剩下的 \(0\) 一次性變成 \(1\) 的方案數
於是 \(cnt_i~=~\sum_{j=1}^{i} cnt_{i-j}~\times~C_{i}^{j}\)
於是遞推一下組合數,遞推一下cnt就做完了。
Code
#include <cstdio>
#ifdef ONLINE_JUDGE
#define freopen(a, b, c)
#endif
#define rg register
#define ci const int
#define cl const long long
typedef long long int ll;
namespace IPT {
const int L = 1000000;
char buf[L], *front=buf, *end=buf;
char GetChar() {
if (front == end) {
end = buf + fread(front = buf, 1, L, stdin);
if (front == end) return -1;
}
return *(front++);
}
}
template <typename T>
inline void qr(T &x) {
rg char ch = IPT::GetChar(), lst = ' ';
while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();
while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();
if (lst == '-') x = -x;
}
template <typename T>
inline void ReadDb(T &x) {
rg char ch = IPT::GetChar(), lst = ' ';
while ((ch > '9') || (ch < '0')) lst = ch, ch = IPT::GetChar();
while ((ch >= '0') && (ch <= '9')) x = x * 10 + (ch ^ 48), ch = IPT::GetChar();
if (ch == '.') {
ch = IPT::GetChar();
double base = 1;
while ((ch >= '0') && (ch <= '9')) x += (ch ^ 48) * ((base *= 0.1)), ch = IPT::GetChar();
}
if (lst == '-') x = -x;
}
namespace OPT {
char buf[120];
}
template <typename T>
inline void qw(T x, const char aft, const bool pt) {
if (x < 0) {x = -x, putchar('-');}
rg int top=0;
do {OPT::buf[++top] = x % 10 + '0';} while (x /= 10);
while (top) putchar(OPT::buf[top--]);
if (pt) putchar(aft);
}
const int maxt = 25;
const int MOD = 998244353;
int n, m, ans;
int C[maxt][maxt], frog[maxt];
void beginning();
int main() {
freopen("1.in", "r", stdin);
qr(n); qr(m);
beginning();
while (m--) {
char ch = '-'; int _cnt = 0;
do {ch = IPT::GetChar();} while ((ch != '0') && (ch != '1'));
do {_cnt += ch - '0', ch = IPT::GetChar();} while ((ch == '0') || (ch == '1'));
int _a = 0; qr(_a);
ans = (ans + 1ll * frog[_cnt] * _a % MOD * frog[n - _cnt]) % MOD;
}
qw(ans, '\n', true);
return 0;
}
void beginning() {
C[0][0] = 1;
for (rg int i = 1; i <= n; ++i) {
C[i][0] = 1;
for (rg int j = 1; j <= i; ++j) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
}
frog[0] = 1;
for (rg int i = 1; i <= n; ++i) {
for (rg int j = 0; j <= i; ++j) {
frog[i] = (frog[i] + 1ll * frog[i - j] * C[i][j]) % MOD;
}
}
}
Summary
當對答案產生貢獻的狀態很少時,考慮直接計算每個狀態的貢獻。