1. 程式人生 > >【組合數學】【P4996】 咕咕咕

【組合數學】【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\)

再到全集的方案數為 \(cnt_{s_1}~\times~cnt_{s_0}\),其中 \(cnt_x\) 為將 \(x\)\(0\) 變成 \(1\) 的方案數

於是答案即為 \(\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

當對答案產生貢獻的狀態很少時,考慮直接計算每個狀態的貢獻。