裏紙

ほぼ競プロ、たまに日記

ARC 059 F - バイナリハック / Unhappy Hacking

問題

F: バイナリハック / Unhappy Hacking - AtCoder Regular Contest 059 | AtCoder

問題概要

キーボードには0,1,Bの3種類のキーがある。Bは空文字列でなければ末尾の一文字を削除する。このキーボードのキーをn回押して文字列sを入力したい時、入力方法は何通り有るか。

  •  1 \le n \le 5000
  •  1 \le |s| \le n

イデア

単純な全探索を考えると、キーを押した回数と現在の文字列の状態を保存することが考えられる。これだと状態数が多すぎるので状態数を減らることを考えてみると、文字列全体を保存する必要はなく、「現在の文字列の長さ」と「何文字目までが合致しているか」を保存すればよいということになる。これで計算量がO(n^3)になり、部分点を取れる(submission)。

これでは満点を得ることは出来ないので、さらに状態数を減らすことを考えたい。ここで、文字列sに注目してみると、文字列s01しか文字列に含まないことから、文字列sと同じ長さの文字列は2^{|s|}通りあることになる。そして、この長さの文字列を完成させるために必要な操作の組み合わせはどれも同じ数だけ存在する。よって、現在の状態を考えるのをやめて、n回キーを操作した時に最終的に長さ|s|の文字列が得られる入力方法の組み合わせを求めて、2^{|s|}で割ったものがその特定の文字列を作れる組み合わせだということになり、計算量はO(n^2)に抑えられ、間に合う。

実装(C++)

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
#define rep(i,n) for(int (i)=0;(i)<(int)(n);++(i))
#define each(itr,c) for(__typeof(c.begin()) itr=c.begin(); itr!=c.end(); ++itr)
#define all(x) (x).begin(),(x).end()
#define pb push_back
#define fi first
#define se second

ll mod=1e9+7;

ll mod_pow(int x, int n)
{
    ll pw[31];
    pw[0]=x;
    for(int i=1; i<31; ++i) pw[i]=(pw[i-1]*pw[i-1])%mod;

    ll ret=1;
    rep(i,31)
    {
        if(n&(1<<i)) (ret*=pw[i])%=mod;
    }
    return ret;
}

ll mod_inv(int x)
{
    return mod_pow(x,mod-2);
}

ll dp[5001][5001]={0};

int main()
{
    int n;
    string s;
    cin >>n >>s;

    int S=s.size();

    dp[0][0]=1;
    rep(i,n)rep(j,i+1)
    {
        (dp[i+1][j+1]+=(dp[i][j]*2)%mod)%=mod;
        (dp[i+1][max(0,j-1)]+=dp[i][j])%=mod;
    }

    ll ans=dp[n][S];
    ans*=mod_inv(mod_pow(2,S));
    cout << ans%mod << endl;
    return 0;
}