mayoko’s diary

プロコンとかいろいろ。

SRM 508 div1 med:YetAnotherORProblem

このDPは思いつかなかった…
良い問題。

解法

まず気づかないといけないのは「和がorの和と等しくなるならすべてのa[i]について2進法で表した時のbitの1がかぶっていない」ということです。軽く説明すると,3つの数を2進数で表した時に
10001
01101
00010
のようになっていると,0bit目の1が1番目の数と2番目の数でかぶっているのでこの場合は和とorの和が等しくなりません。一方で,
10010
01001
00100
のようになっていると,すべての桁で1が被ってないので和とor和が等しくなります。

ということで,このように1が2進数の各桁で被らないような場合の数を求めたいのですが,これを解くためにdpが出てきます。

ちょっと前提ですがstateという変数を用意します。これは,各値a[i]がR[i]未満であるというフラグをn個分並べたものです。
以下のdpではn桁目を見る時「それぞれの数a[i]がR[i]より小さくなっているか」ということを大事にしています。

dp[n][state]=(2進数のn桁目から初めてn番目の桁を見ている時,各a[i]がR[i]未満であるフラグがstateであるような場合の数)
とします。

すると,「stateという状態のもとでa[j]だけがi桁目について1になる」という場合の数は,以下のような状態へ派生します。

j以外の各kについて,R[k]のi桁目が1ならa[k]がR[k]未満であることが確定するのでnext_stateをそのように更新する

で,このnext_stateに状態が遷移するので
dp[n-1][next_state] += dp[n][state]
とやっていけば良いです。

なお,すべての桁が0でも良いのでその場合も特別に設ける必要があります。下のコードではj==nのときすべてのi桁目が0である場合として扱っています。

const ll MOD = 1e9+9;
const int MAXN = 11;
ll dp[62][1<<MAXN];

class YetAnotherORProblem {
public:
    int countSequences(vector<long long> R) {
        memset(dp, 0, sizeof(dp));
        dp[60][0] = 1;
        int n = R.size();
        for (int i = 59; i >= 0; i--) {
            for (int s = 0; s < (1<<n); s++) {
                for (int j = 0; j <= n; j++) {
                    if (j == n || (s>>j)&1 || (R[j]>>i)&1) {
                        // j番目の数のi bit目を1にします
                        // n番目のi bit目を1にする=i bit目は全部0
                        int ns = s;
                        for (int k = 0; k < n; k++) {
                            if (k == j) continue;
                            if ((R[k]>>i)&1) ns |= (1<<k);
                        }
                        (dp[i][ns] += dp[i+1][s]) %= MOD;
                    }
                }
            }
        }
        ll ans = 0;
        for (int i = 0; i < (1<<n); i++) {
            (ans += dp[0][i]) %= MOD;
        }
        return ans;
    }
};