けんちょんの競プロ精進記録

競プロの精進記録や小ネタを書いていきます

TopCoder SRM 452 DIV1 Medium - IOIString (本番 19 人)

すごく面白かった!

スコア: 191.85 / 500.00

問題概要

"I" と "O" のみからなる文字列  T が IOI 文字列であるとは、ある正の整数  a, b が存在して

  •  T_{a} = "I"
  •  T_{a+b} = "O"
  •  T_{a+2b} = "I"

が成立することと定義する (1-indexed)。

いま、"I", "O", "?" のみから成る文字列  S が与えられる。 S の各 "?" を "I" か "O" に置き換える方法のうち、IOI 文字列となるものの個数を 1000000007 で割ったあまりを求めよ。

制約

  •  1 \le |S| \le 2500

考えたこと

面白そう!!!

現代の典型思考方法としては、まずは IOI 文字列の条件をわかりやすく言い換えること。今のままじゃ数えづらいので。また、IOI 文字列を特徴付けるよりも、補集合をとって「IOI 文字列でないための条件」を考えることにする。

まず自明な必要条件として、

  • "IOI" を連続部分文字列として含んだらダメ
  • "IOOOI" を連続部分文字列として含んだらダメ
  • 一般に "I (奇数個の O) I" を連続部分文字列として含んだらダメ

ということが言える。よって、文字列  S 中の "I" の index を  a_{1}, a_{2}, \dots, a_{K} としたとき、この数列の連続する 2 項の差が奇数でなければならないことが言える。さらに、

  •  a が交差が奇数の等差数列でなければならない

ということも言える。もし  a_{i+1} - a_{i} = a_{i} - a_{i-1} となるような箇所が存在すると仮定すると、

  •  a_{i+1} - a_{i-1} は偶数
  •  a_{i-1}, a_{i+1} の中点は "O" となる

となっているのでダメなのだ。たとえば "IOOOIOOOOOI" を想像してみるといい。これは IOI 文字列となってしまっている。

逆に、 a が交差奇数の等差数列であれば、それは IOI 文字列ではない。以上で、わかりやすく特徴付けられた。

数え上げ

"?" の個数を  Q として、"?" を埋める  2^{Q} 通りの方法のうち、IOI 文字列とならないものを数えることにする (全体から引いたものを答える)。

  • 交差  d を固定
  • "I" の index を表す数列  a の初項  a_{1} を固定

としたときに (ここまでで  O(N^{2}))、数列  a の長さ  K が何通りあるかを考えることにする。条件は

  • 文字列  S a_{1} 番目より左側に "I" がない
  • 文字列  S a_{i} 番目と  a_{i+1} 番目の間に "I" がない
  • 文字列  S a_{K} 番目より右側に "I" がない

と表せる。これは予め累積和を管理することで高速に判定できる。

さて、 K としてありうるものは  O(\frac{N}{d}) 通りしかないことに注意すると、全体の計算量は「調和級数の和のオーダー解析」の要領で  O(N^{2} \log N) となる。十分間に合う。

コード

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

const int MOD = 1000000007;
class IOIString {
public:
    int countIOIs(vector <string> mask) {
        string str = "";
        for (auto s: mask) str += s;
        int N = (int)str.size();
        vector<int> inum(N+1, 0), qnum(N+1, 0);
        for (int i = 0; i < N; ++i) {
            inum[i+1] = inum[i], qnum[i+1] = qnum[i];
            if (str[i] == '?') ++qnum[i+1];
            else if (str[i] == 'I') ++inum[i+1];
        }
        vector<long long> two(qnum.back()+1, 1);
        for (int i = 0; i < qnum.back(); ++i) two[i+1] = two[i] * 2 % MOD;

        // 余事象を数える
        long long all = two.back(), sub = 0;

        // I が 0 個
        if (inum.back() == 0) ++sub;

        // I が 1 個
        for (int i = 0; i < N; ++i) {
            if (str[i] != 'O' && inum[i] == 0 && inum.back() - inum[i+1] == 0) ++sub;
        }

        // I が 2 個以上で、交差奇数の等差数列
        for (int d = 1; d <= N; d += 2) {
            for (int i = 0; i < N; ++i) {
                if (str[i] == 'O' || inum[i] > 0) continue;
                int prev = i+1;
                for (int j = i + d; j < N; j += d) {
                    if (str[j] == 'O') break;
                    if (inum[j] - inum[prev] > 0) break;
                    if (inum.back() - inum[j+1] == 0) ++sub;
                    prev = j+1;
                }
            }
        }  
        long long res = (all - sub + MOD) % MOD;
        return res;
    }
};

TopCoder SRM 694 DIV1 Easy - TryShell

いい問題だった。

問題概要

0 以上 255 以下の整数が n 個与えられる。
この n 個の整数を 3 組に分ける方法のうち、各組の xor 和の総和が最大となるものを求めよ。

(制約)
・3 <= n <= 50

解法

dp[ i+1 ][ j ][ k ] := i 番目までみたとき、二組の和がそれぞれ j, k のときの残りの一組の値のとりうる最大値
として解いた。
これなら、以下に示す xor 特有の構造に気づかなくても解ける。

でも、三組の xor 和をそれぞれ a, b, c とし、
n 個全部の整数の xor を sum としたとき、
a ^ b ^ c = sum
なので、
(a ^ b) ^ a ^ b ^ c = (a ^ b) ^ sum
⇔ c = a ^ b ^ sum
なのであった。
このことを利用すればもっと簡明に解ける。

コード

#include <iostream>
#include <sstream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <cstring>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
#include <numeric>
#include <utility>
#include <iomanip>
#include <algorithm>
#include <functional>
using namespace std;

int dp[2][300][300];

class TrySail {
public:
    int get(vector <int> str) {
        int n = str.size();
        int res = 0;
        for (int i = 0; i < 2; ++i) for (int j = 0; j < 300; ++j) for (int k = 0; k < 300; ++k) dp[i][j][k] = -1;
        dp[0][0][0] = 0;
        for (int i = 0; i < str.size(); ++i) {
            for (int j = 0; j < 256; ++j) for (int k = 0; k < 256; ++k) {
                int cur = dp[i%2][j][k];

                chmax(dp[(i+1)%2][j][k], cur^str[i]);
                chmax(dp[(i+1)%2][j^str[i]][k], cur);
                chmax(dp[(i+1)%2][j][k^str[i]], cur);
            }

            for (int j = 0; j < 256; ++j) for (int k = 0; k < 256; ++k) {
                dp[i%2][j][k] = -1;
            }
        }
        for (int j = 0; j < 300; ++j) for (int k = 0; k < 300; ++k) {
            if (dp[n%2][j][k] != -1) {
                chmax(res, j + k + dp[n%2][j][k]);
            }
        }
        return res;
    }
};

TopCoder SRM 604 DIV1 Easy - PowerOfThree

平衡三進法!!!

問題へのリンク

問題概要

二次元座標平面上で、(0, 0) から出発して  (x, y) へと移動したい。何ステップかの移動を行うことができる。

 k ステップ目の移動では、上下左右のいずれかの方向を一つ選んで、 3^{k} だけ移動することができる (いずれかの方向に移動しなければならない)

 (x, y) へ到達することが可能かどうかを判定せよ。

制約

  • [tex -10^{9} \le x, y \le 10^{9}]

考えたこと

いわゆる、平衡三進法展開というやつ!!! 平衡三進法についてはこちら。具体的には、任意の整数を、 1, 3, 9, 27, 81, ... を高々 1 個ずつ用いて、足し引きして表すというもの。

drken1215.hatenablog.com

平衡三進法の重要な性質として、各整数  x 3^{0}, 3^{1}, 3^{2}, \dots から高々 1 個ずつ選んで足し引きして表す方法はただ一つ存在する。よって

  •  x, y を平衡三進法で表したとき
  • 同一の  k に対して
    •  x, y がともに  3^{k} を必要としたらダメ
    •  x, y がともに  3^{k} を必要としないのもダメ

ということがわかる。そうでなければ達成できる。

平衡三進法展開の方法

以下を繰り返す:

  •  x を 3 で割ったあまりが 1 なら --x、2 なら ++x する
  •  x を 3 で割る
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;

vector<int> PowerThree(long long n) {
    vector<int> res;
    while (n != 0) {
        int amari = (n % 3 + 3) % 3;
        if (amari == 1) res.push_back(1), --n;
        else if (amari == 2) res.push_back(-1), ++n;
        else res.push_back(0);
        n /= 3;
    }
    return res;
}

class PowerOfThree {
public:
    string ableToGet(long long x, long long y) {
        vector<int> vx = PowerThree(x);
        vector<int> vy = PowerThree(y);

        // 桁数を合わせる
        while (vx.size() < vy.size()) vx.push_back(0);
        while (vy.size() < vx.size()) vy.push_back(0);
        
        // 各桁比較
        for (int i = 0; i < vx.size(); ++i) {
            if (vx[i] == 0 && vy[i] == 0) return "Impossible";
            if (vx[i] != 0 && vy[i] != 0) return "Impossible";
        }
        return "Possible";
    }
};

TopCoder SRM 402 DIV1 Easy - RandomSort (本番 334 人)

現代の ABC-E にも出そうな DP

問題概要

 1, 2, \dots, N の順列  p_{1}, p_{2}, \dots, p_{N} が与えられる。この順列に対して、以下の操作を繰り返していく

  •  i \lt j かつ  p_{i} \gt p_{j} であるような  (i, j) を一様ランダムに選び、
  •  p_{i} p_{j} を swap する

ソートされた状態になるまでの回数の期待値を求めよ。

制約

  •  1 \le N \le 8

考えたこと

期待値と言われると、期待値の線形性が思い浮かぶが...... N が小さいので DP してしまおう。


dp[p] ← 順列  p をソートするのに要する回数の期待値


計算量は  O(N! N^{2}) となる。

コード

#include <bits/stdc++.h>
using namespace std;
using pint = pair<int, int>;
using pll = pair<long long, long long>;
template<class T> inline bool chmax(T& a, T b) { if (a < b) { a = b; return 1; } return 0; }
template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return 1; } return 0; }

#define REP(i, n) for (long long i = 0; i < (long long)(n); ++i)
#define REP2(i, a, b) for (long long i = a; i < (long long)(b); ++i)
#define COUT(x) cout << #x << " = " << (x) << " (L" << __LINE__ << ")" << endl
template<class T1, class T2> ostream& operator << (ostream &s, pair<T1,T2> P)
{ return s << '<' << P.first << ", " << P.second << '>'; }
template<class T> ostream& operator << (ostream &s, vector<T> P)
{ for (int i = 0; i < P.size(); ++i) { if (i > 0) { s << " "; } s << P[i]; } return s; }
template<class T> ostream& operator << (ostream &s, deque<T> P)
{ for (int i = 0; i < P.size(); ++i) { if (i > 0) { s << " "; } s << P[i]; } return s; }
template<class T> ostream& operator << (ostream &s, vector<vector<T> > P)
{ for (int i = 0; i < P.size(); ++i) { s << endl << P[i]; } return s << endl; }
template<class T> ostream& operator << (ostream &s, set<T> P)
{ for(auto it : P) { s << "<" << it << "> "; } return s; }
template<class T> ostream& operator << (ostream &s, multiset<T> P)
{ for(auto it : P) { s << "<" << it << "> "; } return s; }
template<class T1, class T2> ostream& operator << (ostream &s, map<T1,T2> P)
{ for(auto it : P) { s << "<" << it.first << "->" << it.second << "> "; } return s; }


map<vector<int>, double> dp;
double dfs(vector<int> &v) {
    if (dp.count(v)) return dp[v];
    
    vector<pint> revs;
    for (int i = 0; i < v.size(); ++i) {
        for (int j = i+1; j < v.size(); ++j) {
            if (v[i] > v[j]) revs.emplace_back(i, j);
        }
    }
    if (revs.empty()) return dp[v] = 0.0;
    
    double res = 0;
    for (auto [p, q] : revs) {
        swap(v[p], v[q]);
        res += dfs(v);
        swap(v[p], v[q]);
    }
    return dp[v] = res / revs.size() + 1.0;
}

class RandomSort {
public:
    double getExpected(vector <int> permutation) {
        return dfs(permutation);
    }
};



// BEGIN CUT HERE
namespace moj_harness {
    int run_test_case(int);
    void run_test(int casenum = -1, bool quiet = false) {
        if (casenum != -1) {
            if (run_test_case(casenum) == -1 && !quiet) {
                cerr << "Illegal input! Test case " << casenum << " does not exist." << endl;
            }
            return;
        }
        
        int correct = 0, total = 0;
        for (int i=0;; ++i) {
            int x = run_test_case(i);
            if (x == -1) {
                if (i >= 100) break;
                continue;
            }
            correct += x;
            ++total;
        }
        
        if (total == 0) {
            cerr << "No test cases run." << endl;
        } else if (correct < total) {
            cerr << "Some cases FAILED (passed " << correct << " of " << total << ")." << endl;
        } else {
            cerr << "All " << total << " tests passed!" << endl;
        }
    }
    
    static const double MAX_DOUBLE_ERROR = 1e-9; static bool topcoder_fequ(double expected, double result) { if (isnan(expected)) { return isnan(result); } else if (isinf(expected)) { if (expected > 0) { return result > 0 && isinf(result); } else { return result < 0 && isinf(result); } } else if (isnan(result) || isinf(result)) { return false; } else if (fabs(result - expected) < MAX_DOUBLE_ERROR) { return true; } else { double mmin = min(expected * (1.0 - MAX_DOUBLE_ERROR), expected * (1.0 + MAX_DOUBLE_ERROR)); double mmax = max(expected * (1.0 - MAX_DOUBLE_ERROR), expected * (1.0 + MAX_DOUBLE_ERROR)); return result > mmin && result < mmax; } }
    double moj_relative_error(double expected, double result) { if (isnan(expected) || isinf(expected) || isnan(result) || isinf(result) || expected == 0) return 0; return fabs(result-expected) / fabs(expected); }
    
    int verify_case(int casenum, const double &expected, const double &received, clock_t elapsed) { 
        cerr << "Example " << casenum << "... "; 
        
        string verdict;
        vector<string> info;
        char buf[100];
        
        if (elapsed > CLOCKS_PER_SEC / 200) {
            sprintf(buf, "time %.2fs", elapsed * (1.0/CLOCKS_PER_SEC));
            info.push_back(buf);
        }
        
        if (topcoder_fequ(expected, received)) {
            verdict = "PASSED";
            double rerr = moj_relative_error(expected, received); 
            if (rerr > 0) {
                sprintf(buf, "relative error %.3e", rerr);
                info.push_back(buf);
            }
        } else {
            verdict = "FAILED";
        }
        
        cerr << verdict;
        if (!info.empty()) {
            cerr << " (";
            for (int i=0; i<(int)info.size(); ++i) {
                if (i > 0) cerr << ", ";
                cerr << info[i];
            }
            cerr << ")";
        }
        cerr << endl;
        
        if (verdict == "FAILED") {
            cerr << "    Expected: " << expected << endl; 
            cerr << "    Received: " << received << endl; 
        }
        
        return verdict == "PASSED";
    }

    int run_test_case(int casenum__) {
        switch (casenum__) {
        case 0: {
            int permutation[]         = {1,3,2};
            double expected__         = 1.0;

            clock_t start__           = clock();
            double received__         = RandomSort().getExpected(vector <int>(permutation, permutation + (sizeof permutation / sizeof permutation[0])));
            return verify_case(casenum__, expected__, received__, clock()-start__);
        }
        case 1: {
            int permutation[]         = {4,3,2,1};
            double expected__         = 4.066666666666666;

            clock_t start__           = clock();
            double received__         = RandomSort().getExpected(vector <int>(permutation, permutation + (sizeof permutation / sizeof permutation[0])));
            return verify_case(casenum__, expected__, received__, clock()-start__);
        }
        case 2: {
            int permutation[]         = {1};
            double expected__         = 0.0;

            clock_t start__           = clock();
            double received__         = RandomSort().getExpected(vector <int>(permutation, permutation + (sizeof permutation / sizeof permutation[0])));
            return verify_case(casenum__, expected__, received__, clock()-start__);
        }
        case 3: {
            int permutation[]         = {2,5,1,6,3,4};
            double expected__         = 5.666666666666666;

            clock_t start__           = clock();
            double received__         = RandomSort().getExpected(vector <int>(permutation, permutation + (sizeof permutation / sizeof permutation[0])));
            return verify_case(casenum__, expected__, received__, clock()-start__);
        }

        // custom cases

/*      case 4: {
           int permutation[]         = ;
           double expected__         = ;

           clock_t start__           = clock();
           double received__         = RandomSort().getExpected(vector <int>(permutation, permutation + (sizeof permutation / sizeof permutation[0])));
           return verify_case(casenum__, expected__, received__, clock()-start__);
       }*/
/*      case 5: {
           int permutation[]         = ;
           double expected__         = ;

           clock_t start__           = clock();
           double received__         = RandomSort().getExpected(vector <int>(permutation, permutation + (sizeof permutation / sizeof permutation[0])));
           return verify_case(casenum__, expected__, received__, clock()-start__);
       }*/
/*      case 6: {
           int permutation[]         = ;
           double expected__         = ;

           clock_t start__           = clock();
           double received__         = RandomSort().getExpected(vector <int>(permutation, permutation + (sizeof permutation / sizeof permutation[0])));
           return verify_case(casenum__, expected__, received__, clock()-start__);
       }*/
        default:
            return -1;
        }
    }
}
 

int main(int argc, char *argv[]) {
    if (argc == 1) {
        moj_harness::run_test();
    } else {
        for (int i=1; i<argc; ++i)
            moj_harness::run_test(atoi(argv[i]));
    }
}
// END CUT HERE

TopCoder SRM 401 DIV1 Easy - FIELDDiagrams (本番 176 人)

素朴な DP でもできるけど、実はカタラン数!

問題概要

正の整数  N が与えられる。以下の条件を満たす数列  a = (a_{1}, a_{2}, \dots, a_{K}) (数列のサイズ  K 1 以上の範囲で自由に選んでよい) の個数を求めよ。

  •  i に対して、 1 \le a_{i} \le N - i + 1 である
  •  a_{1} \ge a_{2} \ge \dots \ge a_{K} である

たとえば、 N = 2 のとき、 a = (2), (2, 1), (1), (1, 1) の 4 通りがある。

制約

  •  1 \le N \le 30

考えたこと

素朴に DP でもできるが、実はカタラン数!!

たとえば  N = 5 のとき、 a = (5, 3, 2, 2) は条件を満たすが、これは下図の左のように図示できる。そしてそれは、下図の右のように  (N+1) \times (N+1) グリッドの最短経路に対応する。

 a_{1} \ge a_{2} \le \dots \le a_{K} という条件は、グリッドの対角線の下側のみを使うということに対応する。これはカタラン数そのものである。

よって、答えは「 N+1 番目のカタラン数 - 1」である (1 を引くのは右下を通る最短経路を除外するため)。つまり、


 \displaystyle \frac{_{2N}\mathrm{C}_{N}}{N+1} - 1


を答えればよい。計算量は  O(N) となる。

コード

オーバーフロー対策のために、__int128_t 型を用いた。

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

// オーバーフロー対策
using u128 = __int128_t;

class FIELDDiagrams {
public:
    long long countDiagrams(int fieldOrder) {
        long long N = fieldOrder + 1;
        
        // N 番目のカタラン数 - 1
        // C(2N, N) / (N+1)
        u128 res = 1;
        for (long long i = 1; i <= N; ++i) {
            res *= N*2 - i + 1;
            res /= i;
        }
        res /= (N+1);  // N 番目のカタラン数
        return (res - 1);
    }
};

// BEGIN CUT HERE
namespace moj_harness {
    int run_test_case(int);
    void run_test(int casenum = -1, bool quiet = false) {
        if (casenum != -1) {
            if (run_test_case(casenum) == -1 && !quiet) {
                cerr << "Illegal input! Test case " << casenum << " does not exist." << endl;
            }
            return;
        }
        
        int correct = 0, total = 0;
        for (int i=0;; ++i) {
            int x = run_test_case(i);
            if (x == -1) {
                if (i >= 100) break;
                continue;
            }
            correct += x;
            ++total;
        }
        
        if (total == 0) {
            cerr << "No test cases run." << endl;
        } else if (correct < total) {
            cerr << "Some cases FAILED (passed " << correct << " of " << total << ")." << endl;
        } else {
            cerr << "All " << total << " tests passed!" << endl;
        }
    }
    
    int verify_case(int casenum, const long long &expected, const long long &received, clock_t elapsed) { 
        cerr << "Example " << casenum << "... "; 
        
        string verdict;
        vector<string> info;
        char buf[100];
        
        if (elapsed > CLOCKS_PER_SEC / 200) {
            sprintf(buf, "time %.2fs", elapsed * (1.0/CLOCKS_PER_SEC));
            info.push_back(buf);
        }
        
        if (expected == received) {
            verdict = "PASSED";
        } else {
            verdict = "FAILED";
        }
        
        cerr << verdict;
        if (!info.empty()) {
            cerr << " (";
            for (int i=0; i<(int)info.size(); ++i) {
                if (i > 0) cerr << ", ";
                cerr << info[i];
            }
            cerr << ")";
        }
        cerr << endl;
        
        if (verdict == "FAILED") {
            cerr << "    Expected: " << expected << endl; 
            cerr << "    Received: " << received << endl; 
        }
        
        return verdict == "PASSED";
    }

int run_test_case(int casenum__) {
    switch (casenum__) {
        case 0: {
            int fieldOrder            = 2;
            long long expected__      = 4;
            
            clock_t start__           = clock();
            long long received__      = FIELDDiagrams().countDiagrams(fieldOrder);
            return verify_case(casenum__, expected__, received__, clock()-start__);
        }
        case 1: {
            int fieldOrder            = 3;
            long long expected__      = 13;
            
            clock_t start__           = clock();
            long long received__      = FIELDDiagrams().countDiagrams(fieldOrder);
            return verify_case(casenum__, expected__, received__, clock()-start__);
        }
        case 2: {
            int fieldOrder            = 5;
            long long expected__      = 131;
            
            clock_t start__           = clock();
            long long received__      = FIELDDiagrams().countDiagrams(fieldOrder);
            return verify_case(casenum__, expected__, received__, clock()-start__);
        }
            
            // custom cases
            
        case 3: {
            int fieldOrder            = 29;
            long long expected__      = 3814986502092303LL;
            
            clock_t start__           = clock();
            long long received__      = FIELDDiagrams().countDiagrams(fieldOrder);
            return verify_case(casenum__, expected__, received__, clock()-start__);
        }
        case 4: {
            int fieldOrder            = 30;
            long long expected__      = 14544636039226908LL;
            
            clock_t start__           = clock();
            long long received__      = FIELDDiagrams().countDiagrams(fieldOrder);
            return verify_case(casenum__, expected__, received__, clock()-start__);
        }
            /*      case 5: {
             int fieldOrder            = ;
             long long expected__      = ;
             
             clock_t start__           = clock();
             long long received__      = FIELDDiagrams().countDiagrams(fieldOrder);
             return verify_case(casenum__, expected__, received__, clock()-start__);
             }*/
        default:
            return -1;
    }
}
}
 

int main(int argc, char *argv[]) {
    if (argc == 1) {
        moj_harness::run_test();
    } else {
        for (int i=1; i<argc; ++i)
            moj_harness::run_test(atoi(argv[i]));
    }
}
// END CUT HERE