ARC003 C - 暗闇帰り道

C: 暗闇帰り道 - AtCoder Regular Contest 003 | AtCoder

  • 二分探索 + bfs な問題。
  • 以前解いたのにもかかわらず間違えまくった...。
  • bfs では pop 直後に vis を見てはじいてしまったり(致命的)、high = 1<<30 から始めたり、val = min(val,pow(0.99,t)*..) を保持しておこうとしたり...無駄なことを。シンプルに書くことを心がけよう。
class C_DarknessWayHome {
public:
    void solve(void) {
        int N,M;
        cin>>N>>M;
        vector<string> field(N);

        int sx,sy,gx,gy;
        REP(i,N)
        {
            cin>>field[i];
            REP(j,M)
            {
                if (field[i][j] == 'g')
                { gx = j; gy = i; }
                if (field[i][j] == 's')
                { sx = j; sy = i; }
            }
        }


        const int dx[] = {-1,0,1,0};
        const int dy[] = {0,1,0,-1};

        // low より明るい状態で goal までたどりつけるか bfs
        auto bfs = [&](double low) {
            queue<tuple<int,int,int>> que;
            que.emplace(sx,sy,0);

            vector<vector<bool>> vis(N,vector<bool>(M,false));
            while ( !que.empty() )
            {
                int x,y,t;
                tie(x,y,t) = que.front();
                que.pop();

                REP(d,4)
                {
                    int nx = x+dx[d];
                    int ny = y+dy[d];

                    if (nx < 0 || M <= nx || ny < 0 || N <= ny)
                        continue;
                    if (field[ny][nx] == '#')
                        continue;
                    if (field[ny][nx] == 's')
                        continue;
                    if (field[ny][nx] == 'g')
                        return true;
                    if ( vis[ny][nx] )
                        continue;

                    double val = pow(0.99,t+1)*(field[ny][nx]-'0');
                    if (val < low)
                        continue;
                    vis[ny][nx] = true;
                    que.emplace(nx,ny,t+1);
                }
            }
            return false;
        };

        // ゴールに到達しないケースを先にやる
        if ( !bfs(0.0) )
        {
            cout<<-1<<endl;
            return;
        }
        double low  = 0;
        double high = 10;

        // fabs(low-high) < EPS の EPS が小さすぎるとループ回数が
        // 大きくなって TLE してしまうので最大 100 回でやる
        //
        // O(100*N*M)
        REP(i,100)
        {
            if (fabs(low-high) < 1e-10)
                break;
            double mid = (low+high)*0.5;
            if ( bfs(mid) )
                low = mid;
            else
                high = mid;
        }
        cout<<setprecision(20)<<low<<endl;
    }
};

No.206 数の積集合を求めるクエリ

No.206 数の積集合を求めるクエリ - yukicoder

  • A[i],B[i] の上限が高々 10^5 であることに着目して解く。
  • 以下は他の人のコードを参考にして解いた。bitset 使うとこんなに簡単に書けるとは..。すげー。
class QueryOfNumberProductSet
{
public:
    void solve(void)
    {
            int L,M,N;
            cin>>L>>M>>N;

            // A[i],B[i] の上限が高々 10^5 であること
            // A,B にて同じ数字が存在しないこと
            // から A,B を bitset で扱うと A & B の計算、 B の一律加算が簡単に表現できる
            bitset<100001> A;
            bitset<100001> B;

            REP(i,L)
            {
                int x;
                cin>>x;
                A.set(x);
            }
            REP(i,M)
            {
                int x;
                cin>>x;
                B.set(x);
            }

            int Q;
            cin>>Q;

            // O(Q*N/64)
            REP(v,Q)
            {
                // 加算によってビットが左にずれる
                cout<<(A & (B<<v)).count()<<endl;
            }
    }
};
  • fft をつかった O(N*log(N)+Q) 解法がステキ
  • とはいえフーリエ変換についてはライブラリのを貼り付けただけなんで、ちゃんと勉強しないと..。
class QueryOfNumberProductSet
{
public:
    void solve(void)
    {
            int L,M,N;
            cin>>L>>M>>N;

            //
            // f(x) = ∑ x^A[i]
            // g(x) = ∑ x^(N-B[i])  (次数がN-B[i]なのは畳み込み後の次数に A[i]-B[i] を入れたいから)
            //
            // とするとき
            //
            // (f*g)(x) = ∑∑ a[i]b[j] x^(A[i]-B[j]+N)
            //          = ∑ c[i] x^i
            //
            // c[N+v] = #{(i,j) | (A[i]-B[j]+N == N+v) } となる
            //
            // よって FFT が使える
            //

            vector<double> a(N+1,0);
            vector<double> b(N+1,0);
            vector<double> c(2*(N+1),0);

            REP(i,L)
            {
                int x;
                cin>>x;
                // 次数に対応する係数だけ 1 にする
                a[x] = 1;
            }

            REP(i,M)
            {
                int x;
                cin>>x;
                b[N-x] = 1;
            }

            convolve(a,b,c);

            int Q;
            cin>>Q;
            REP(v,Q)
            {   // round してから cast しないと期待どおりの値にならない
                cout<<(int)round(c[N+v])<<endl;
            }
    }
};

No.209 Longest Mountain Subsequence

No.209 Longest Mountain Subsequence - yukicoder

  • 何も考えることもない単純な DP。のはずが結構バグを作ってしまった...。
  • O(T*N^3) じゃ間に合わないので DP 部分を O(N^2) にしないとダメなのかなぁと思いきやそうでもなかった。
class LongestMountainSubsequence
{
public:
    void solve(void)
    {
        int T;
        cin>>T;

        // O(N^3*T)
        REP(i,T)
        {
            int N;
            cin>>N;

            vector<ll> A(N);
            REP(i,N) cin>>A[i];

            vector<vector<int>> dpL(N,vector<int>(N,0));
            vector<vector<int>> dpR(N,vector<int>(N,0));

            REP(i,N) dpL[i][i] = dpR[i][i] = 1;

            REP(i,N)
            FOR(j,i+1,N)
            {
                if (A[i] < A[j])
                    dpL[i][j] = 2;
                if (A[i] > A[j])
                    dpR[i][j] = 2;
            }

            REP(i,N)
            FOR(j,i,N)
            FOR(k,j,N)
            {
                if ( A[i] < A[j] && A[j] < A[k] && abs(A[i]-A[j]) < abs(A[j]-A[k]) )
                    dpL[j][k] = max(dpL[j][k], dpL[i][j]+1);
            }
            // 単調減少の方は逆順のループ
            for (int k = N-1; k >= 0; --k)
            for (int j = k; j >= 0; --j)
            for (int i = j; i >= 0; --i)
            {
                if ( A[i] > A[j] && A[j] > A[k] && abs(A[i]-A[j]) > abs(A[j]-A[k]) )
                    dpR[i][j] = max(dpR[i][j], dpR[j][k]+1);
            }

            int cnt = 1;
            // 単調増加・単調現象あわせたもの
            REP(i,N)
            REP(j,i)
            FOR(k,i+1,N)
            {
                // A[i] は二重にカウントされているので -1
                cnt = max(cnt, dpL[j][i] + dpR[i][k] - 1);
            }
            // 単調部分のみ
            REP(i,N)
            FOR(j,i+1,N)
                cnt = max({cnt, dpL[i][j], dpR[i][j]});
            cout<<cnt<<endl;
        }
    }
};

No.184 たのしい排他的論理和(HARD)

No.184 たのしい排他的論理和(HARD) - yukicoder

  • uint64 を 64列 x N 行の mod 2 上の行列と考えて rank をもとめればよい。
  • col = 63 にすべきところを col = 64 にしたりしてハマった。
// uint64_t 吐き出し法
std::vector<uint64_t>
sweep_uint64(const std::vector<uint64_t> &A)
{
        auto a = A;
        int  n = a.size();

        int row = 0;
        // O(64*N)
        for (int col = 63; col >= 0; --col)
        {
            int pivot = row; //row 行目以降だけ見る
            // 先頭から col 番目のビットが立っている a[i] を見つける
            while ( pivot < n && !(a[pivot] & 1ULL<<col) )
                ++pivot;
            // col 番目にビットが立つ a はなかった
            if (pivot == n)
                continue;

            std::swap(a[row], a[pivot]);
            for (int i = row+1; i < n; ++i)
            {
                if ( a[i] & 1ULL<<col)
                    a[i] ^= a[row];
            }
            ++row;
        }
        return a;
}

using namespace std;

class HappyXorHard
{
public:
    void solve(void) {
        int N;
        cin>>N;
        vector<uint64_t> A(N);
        REP(i,N) cin>>A[i];

        A = sweep_uint64(A);

        int k = count_if(RANGE(A), [](uint64_t a) { return (a>0); });
        cout<<(1LL<<k)<<endl;
    }
};

No.186 中華風 (Easy)

No.186 中華風 (Easy) - yukicoder

  • 中国剰余定理を使う問題。
  • 蟻本に乗ってたライブラリを使ってしまったが、これくらい自力でかけないとダメかなぁ...。
  • 正整数を出力するので 0 や負は変換しないとダメ。
template <typename T>
std::pair<T,T> linear_congruence(
    const std::vector<T> &A,
    const std::vector<T> &B,
    const std::vector<T> &M)
{
        T x = 0, m = 1;
        for (int i = 0; i < A.size(); ++i)
        {
            T a = A[i] * m;
            T b = B[i] - A[i] * x;
            T d = gcd(M[i], a);
            if (b % d) // solution does not exist.
                return std::make_pair(0, -1);
            T md = M[i]/d;
            T t = b / d * mod_inverse(a / d, M[i] / d) % (M[i] / d);
            x = x + m * t;
            m *= M[i] / d;
        }
        return std::make_pair(x % m, m);
}
class ChiniseStyleEasy
{
public:
    void solve(void)
    {
        vector<ll> x(3);
        vector<ll> y(3);

        REP(i, 3)
            cin>>x[i]>>y[i];
        vector<ll> a(3,1);
        ll z,m;
        tie(z,m) = linear_congruence(a,x,y);

        if (m < 0)
        {
            cout<<-1<<endl;
            return;
        }
        ll Y = max({y[0],y[1],y[2]});

        if ( z < 0 )
            z += (-z/m+1)*m;
        ll X = Y/m*m + z;
        if ( X == 0 )
            cout<<m<<endl;
        else
            cout<<X<<endl;
    }
};

No.147 試験監督(2)

No.147 試験監督(2) - yukicoder

  • 繰り返し2乗法を使うもんだい。フィボナッチ数列が隠れているとは...
  • 行列繰り返し2乗法で高速化できないか?というのはテクニックとして覚えておこう。(蟻本で見てはいたんだけどなぁ...)
const ll Mod = (int)(1E+9)+7;
class Invigilator2 {
public:
    //
    // x^(10^k) = x^(10^(k-1)*10)
    //          = x^(10^(k-1)) * ... * x^(10^(k-1))
    // なので計算量が落とせる
    ll tenPow(ll a, string k) {
        reverse(RANGE(k));

        ll ret = 1LL;
        ll prev = a; // a^(10^(k-1))
        // O(|k|*10) <= 2000
        for (auto c : k)
        {
            ll cur = 1LL;
            ll ten = 1LL;

            REP(i,c-'0')
                (cur *= prev) %= Mod;
            (ret *= cur) %= Mod;
            REP(i,10)
                (ten *= prev) %= Mod;
            prev = ten;
        }
        return ret;
    }
    // P(x) := x 個いすがある机で x 番目に座る
    // Q(x) := x 個いすがある机で x 番目に座らない
    // とすると
    //
    // P(x+1) = Q(x)
    // Q(x+1) = P(x)+Q(x)
    // の関係式となる
    //
    // R(x) = P(x)+Q(x) が求める組み合わせ数で
    // R(x+1) = Q(x) + P(x) + Q(x)
    //        = R(x) + R(x-1)
    // R(0) = 1
    // R(1) = 2
    //
    // よりこれはフィボナッチ数列となる
    // フィボナッチ数は行列累乗で O(log(n)) で解ける
    //
    // |R(x+1)|   |1  1| |R(x)  |
    // |      | = |    |*|      |
    // |R(x)  |   |1  0| |R(x-1)|
    //
    // S(x+1) = B*S(x)
    // S(x) = B^x * S(0)
    //
    typedef array<ll,4> Mat;
    Mat mul(const Mat &a, const Mat &b) {
        return Mat{(a[0]*b[0]%Mod+a[1]*b[2]%Mod)%Mod,
                   (a[0]*b[1]%Mod+a[1]*b[3]%Mod)%Mod,
                   (a[2]*b[0]%Mod+a[3]*b[2]%Mod)%Mod,
                   (a[2]*b[1]%Mod+a[3]*b[3]%Mod)%Mod};
    }
    Mat mpow(const Mat &x, ll k) {
        Mat  a = x;
        Mat  b{1,0,
               0,1};
        while (k > 0)
        {
            if (k & 1)
                b = mul(b,a);
            a = mul(a,a);
            k >>= 1;
        }
        return b;
    }
    ll calc(ll x) {
        auto a = mpow(Mat{1,1,1,0}, x);
        return mul(a, Mat{2,0,
                          1,0})[2];
    }
    void solve(void) {
        int N;
        cin>>N;
        vector<ll> C(N);
        vector<string> D(N);
        REP(i,N)
        {
            cin>>C[i]>>D[i];
        }
        ll cnt = 1LL;
        // O(N*(2000+log(D[i]))) <= 6*10^7
        REP(i,N)
        {
            (cnt *= tenPow(calc(C[i]), D[i])) %= Mod;
        }
        cout<<cnt<<endl;
    }
};

No.205 マージして辞書順最小

No.205 マージして辞書順最小 - yukicoder

  • 貪欲法。priority_queue で書くより set で書く方が綺麗にかけたかも。priority_queue の compare 関数がうまく実装できなかった...。
class MergeAndLexicographicOrderSort
{
public:
    void solve(void)
    {
            int N;
            cin>>N;

            auto lex_comp = [](string a, string b) {
                return b<a;
            };
            priority_queue<string,vector<string>,decltype(lex_comp)> S(lex_comp);
            REP(i,N)
            {
                string s;
                cin>>s;
                // z < za の優先度にしてほしいので、末尾に全ての文字コードよりも
                // 優先度の低い文字コード ~ を追加しておく
                S.push(s+'~');
            }

            string T;
            while (!S.empty())
            {
                string s = S.top();
                S.pop();
                T += s[0];
                s = s.substr(1);
                // 末尾の ~ まで達したら再挿入はなし
                if (s.length() > 1)
                    S.push(s);
            }
            cout<<T<<endl;
    }
};