ferinの競プロ帳

競プロについてのメモ

CODE FESTIVAL 2017 Final A - AKIBA

問題ページ
A - AKIBA

考えたこと

  1. Aを飛ばしてKIHBRになればYESという方針で実装を始める
  2. 300だしどうせ通るだろーと軽い気持ちでいたら落ちる
  3. 実装バグらせたのを見つけて直して出すと落ちる
  4. "KIHBRAA"、"KAIBHR"あたりのケースを見逃しまくっていた

どうせ300だし一瞬で解けるだろうと思って解いたらバグらせまくり無駄に複雑な実装にして20分かけて反省。埋め込みか2^5全列挙で書くべきだった。

//#define __USE_MINGW_ANSI_STDIO 0
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
#define int ll
typedef vector<int> VI;
typedef vector<VI> VVI;
typedef vector<ll> VL;
typedef vector<VL> VVL;
typedef pair<int, int> PII;

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define ALL(x) x.begin(), x.end()
#define IN(a, b, x) (a<=x&&x<b)
#define MP make_pair
#define PB push_back
#ifdef int
const ll INF = (1LL<<60);
#else
const int INF = (1LL<<30);
#endif
const double PI = 3.14159265359;
const double EPS = 1e-12;
const int MOD = 1000000007;

template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); }
template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); }

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

signed main(void)
{
  string s;
  cin >> s;
  string t = "KIHBR$$$$";
  int cnt = 0, tmp = 0;
  if(s.size() >= 10) {
    cout << "NO" << endl;
    return 0;
  }
  REP(i, s.size()) {
    if(s[i] == 'A') i++, tmp++;
    else tmp = 0;
    if(tmp > 1 || (cnt == 1 && tmp > 0) || (cnt == 2 && tmp > 0)) {
      cout << "NO" << endl;
      return 0;
    }
    if(s[i] != 'A') tmp = 0;
    if(i<s.size() && s[i] != t[cnt++]) {
      cout << "NO" << endl;
      return 0;
    }
  }
  if(cnt != 5) cout << "NO" << endl;
  else cout << "YES" << endl;

  return 0;
}

CODE FESTIVAL 2014 予選A D - 壊れた電卓

問題ページ
D: 壊れた電卓 - CODE FESTIVAL 2014 予選A | AtCoder

考えたこと

  1. 桁DPコンテストの問題として見たので桁DPを考える
  2. 使った数をbitで持ってi桁目までで差がもっとも小さい数を持つDPを考える
  3. dp[i桁目まで見た][使った数の集合]としてDPをする
  4. i桁目を決定するときA[i]との差がもっとも小さい数としたらサンプル通ったので投げると落ちる
  5. いろいろ試してるとi桁目の決定でおかしいパターンがあることに気づく
  6. i桁目までですでにAより小さいことが決定していれば使える最大の数、大きいことが決定していれば最小の数を使うべき
  7. dp[i桁目まで見た][使った数の集合][大小フラグ]としてDPして投げると通る

学び

#define __USE_MINGW_ANSI_STDIO 0
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
#define int ll
typedef vector<int> VI;
typedef vector<VI> VVI;
typedef vector<ll> VL;
typedef vector<VL> VVL;
typedef pair<int, int> PII;

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define ALL(x) x.begin(), x.end()
#define IN(a, b, x) (a<=x&&x<b)
#define MP make_pair
#define PB push_back
#ifdef int
const ll INF = (1LL<<60);
#else
const int INF = (1LL<<30);
#endif
const double PI = 3.14159265359;
const double EPS = 1e-12;
const int MOD = 1000000007;

template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); }
template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); }

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

int dp[20][1LL<<10][3];
signed main(void)
{
  int A;
  int K;
  cin >> A >> K;
  string a = to_string(A);
  int n = a.size();

  memset(dp, -1, sizeof(dp));
  dp[0][0][0] = 0;
  REP(i, n) {
    REP(j, 1<<10) REP(k, 3) {
      if(dp[i][j][k] == -1) continue;
      REP(d, 10) {
        int tmp = j;
        if(!(j & (1<<d))) tmp |= 1<<d;
        // tmpで桁が1の中からどれを選ぶのが最善か?
        if(k == 0) {
          if(tmp & 1<<(a[i]-'0')) dp[i+1][tmp][0] = dp[i][j][k]*10 + a[i]-'0';
          REP(l, a[i]-'0') if(tmp & 1<<l) {
            chmax(dp[i+1][tmp][1], dp[i][j][k]*10 + l);
          }
          for(int l=9; l>a[i]-'0'; --l) if(tmp & 1<<l) {
            if(dp[i+1][tmp][2] == -1) dp[i+1][tmp][2] = dp[i][j][k]*10 + l;
            chmin(dp[i+1][tmp][2], dp[i][j][k]*10 + l);
          }
        }
        // 小さいのが確定
        else if(k == 1) {
          REP(l, 10) if(tmp & 1<<l) {
            chmax(dp[i+1][tmp][1], dp[i][j][k]*10 + l);
          }
        }
        // 大きいのが確定
        else if(k == 2) {
          for(int l=9; l>=0; --l) {
            if(tmp & 1<<l) {
              if(dp[i+1][tmp][2] == -1) dp[i+1][tmp][2] = dp[i][j][k]*10 + l;
              chmin(dp[i+1][tmp][2], dp[i][j][k] * 10 + l);
            }
          }
        }
      }
    }
  }

  int ans = INF;
  REP(i, 1<<10) REP(k, 3) {
    int cnt = 0;
    REP(j, 10) if(i & (1<<j)) cnt++;
    if(cnt <= K) {
      if(dp[n][i][k] != -1 && abs(ans-A) > abs(dp[n][i][k]-A)) {
        ans = dp[n][i][k];
      }
    }
  }
  int r = 0;
  REP(i, n-1) r *= 10, r += 9;
  cout << min(abs(A-ans), abs(A-r)) << endl;

  return 0;
}

Codeforces Round #443 (Div. 1) A. Short Program

問題ページ
Problem - 878A - Codeforces

概要

n個(n <= 10^5)のbit操作が書かれたプログラムが与えられる。0から1023までの数がプログラムに入力される。このとき、5個以下のbit操作で同じ操作となるプログラムを出力しろ。

解法

bit操作と言われたので2進数で考える。
まず、sampleを試してみる。-が入力された数そのままの桁、~が入力された数から反転した桁を表す。
sample1

    ----------
| 3 --------11
^ 2 --------01
| 1 --------01

sample2

    ----------
& 1 000000000-
& 3 000000000-
& 5 000000000-

sample3

    ----------
^ 1 ---------~
^ 2 --------~~
^ 3 ----------

こんな感じで試してみると操作1回についてO(logX)で状態を遷移できるのでO(NlogX)で最後の状態が求まることがわかる。
最後の状態にするためには少なくともANDとORとXORを1回ずつ使えば実現可能である。最後の状態が-と~のところを残すようにANDを取り、1のところにORを取り、~のところにXORを取ることで3回のbit操作で実現可能である。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
#define int ll

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)

int dp[20];
signed main(void)
{
  int n;
  cin >> n;
  memset(dp, -1, sizeof(dp));
  REP(i, n) {
    char c;
    int x;
    cin >> c >> x;
    if(c == '&') {
      REP(i, 10) {
        if(!(x & (1<<i))) {
          dp[i] = 0;
        }
      }
    } else if(c == '|') {
      REP(i, 10) {
        if(x & (1<<i)) {
          dp[i] = 1;
        }
      }
    } else {
      REP(i, 10) {
        if(x & (1<<i)) {
          if(dp[i] == -1) dp[i] = 2;
          else if(dp[i] == 2) dp[i] = -1;
          else if(dp[i] == 0) dp[i] = 1;
          else if(dp[i] == 1) dp[i] = 0;
        }
      }
    }
  }


  int a = 0, b = 0, c = 0;
  REP(i, 10) {
    if(dp[i] == -1) a |= 1<<i;
    else if(dp[i] == 1) b |= 1<<i;
    else if(dp[i] == 2) a |= 1<<i, c |= 1<<i;
  }

  cout << 3 << endl;
  cout << "& " << a << endl;
  cout << "| " << b << endl;
  cout << "^ " << c << endl;

  return 0;
}

Codeforces Round #444 (Div. 2) C. Solution for Cube

問題ページ
Problem - 887C - Codeforces

概要

2×2×2の立方体のルービックキューブが入力で与えられる。一回回転することで色が揃えられるなら"YES"、揃わなければ"NO"を出力する。

解法

回転のパターンは6パターンで反転で2倍で12パターン存在する。これをすべて試して揃うものがあればYESを出力する。
回転する位置を配列で持っておき、配列から回転と判定をするようにすると実装量が落ちて楽。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
#define int ll
typedef vector<int> VI;

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define ALL(x) x.begin(), x.end()

int a[24], b[24];

bool check() {
  REP(i, 6) {
    bool flag = true;
    FOR(j, 1, 4) {
      if(a[i*4] != a[i*4+j]) {
        flag = false;
        break;
      }
    }
    if(!flag) return false;
  }
  return true;
}

signed main(void)
{
  REP(i, 24) cin >> a[i], b[i] = a[i];

  int tmp1, tmp2;
  VI v;

  // 1
  v = {1, 3, 5, 7, 9, 11, 22, 20};
  tmp1 = a[v[0]], tmp2 = a[v[1]];
  for(int i=0; i<6; i+=2) {
    a[v[i]] = a[v[i+2]];
    a[v[i+1]] = a[v[i+3]];

  }
  a[v[6]] = tmp1, a[v[7]] = tmp2;

  if(check()) {
    cout << "YES" << endl;
    return 0;
  }
  REP(i, 24) a[i] = b[i];
  reverse(ALL(v));
  tmp1 = a[v[0]], tmp2 = a[v[1]];
  for(int i=0; i<6; i+=2) {
    a[v[i]] = a[v[i+2]];
    a[v[i+1]] = a[v[i+3]];

  }
  a[v[6]] = tmp1, a[v[7]] = tmp2;

  if(check()) {
    cout << "YES" << endl;
    return 0;
  }
  REP(i, 24) a[i] = b[i];

  // 2
  v = {0, 2, 4, 6, 8, 10, 23, 21};
  tmp1 = a[v[0]], tmp2 = a[v[1]];
  for(int i=0; i<6; i+=2) {
    a[v[i]] = a[v[i+2]];
    a[v[i+1]] = a[v[i+3]];

  }
  a[v[6]] = tmp1, a[v[7]] = tmp2;

  if(check()) {
    cout << "YES" << endl;
    return 0;
  }
  REP(i, 24) a[i] = b[i];
  reverse(ALL(v));
  tmp1 = a[v[0]], tmp2 = a[v[1]];
  for(int i=0; i<6; i+=2) {
    a[v[i]] = a[v[i+2]];
    a[v[i+1]] = a[v[i+3]];

  }
  a[v[6]] = tmp1, a[v[7]] = tmp2;

  if(check()) {
    cout << "YES" << endl;
    return 0;
  }
  REP(i, 24) a[i] = b[i];

  // 3
  v = {12, 13, 4, 5, 16, 17, 20, 21};
  tmp1 = a[v[0]], tmp2 = a[v[1]];
  for(int i=0; i<6; i+=2) {
    a[v[i]] = a[v[i+2]];
    a[v[i+1]] = a[v[i+3]];

  }
  a[v[6]] = tmp1, a[v[7]] = tmp2;

  if(check()) {
    cout << "YES" << endl;
    return 0;
  }
  REP(i, 24) a[i] = b[i];
  reverse(ALL(v));
  tmp1 = a[v[0]], tmp2 = a[v[1]];
  for(int i=0; i<6; i+=2) {
    a[v[i]] = a[v[i+2]];
    a[v[i+1]] = a[v[i+3]];

  }
  a[v[6]] = tmp1, a[v[7]] = tmp2;

  if(check()) {
    cout << "YES" << endl;
    return 0;
  }
  REP(i, 24) a[i] = b[i];

  // 4
  v = {14, 15, 6, 7, 18, 19, 22, 23};
  tmp1 = a[v[0]], tmp2 = a[v[1]];
  for(int i=0; i<6; i+=2) {
    a[v[i]] = a[v[i+2]];
    a[v[i+1]] = a[v[i+3]];

  }
  a[v[6]] = tmp1, a[v[7]] = tmp2;

  if(check()) {
    cout << "YES" << endl;
    return 0;
  }
  REP(i, 24) a[i] = b[i];
  reverse(ALL(v));
  tmp1 = a[v[0]], tmp2 = a[v[1]];
  for(int i=0; i<6; i+=2) {
    a[v[i]] = a[v[i+2]];
    a[v[i+1]] = a[v[i+3]];

  }
  a[v[6]] = tmp1, a[v[7]] = tmp2;

  if(check()) {
    cout << "YES" << endl;
    return 0;
  }
  REP(i, 24) a[i] = b[i];

  // 5
  v = {2, 3, 16, 18, 9, 8, 15, 13};
  tmp1 = a[v[0]], tmp2 = a[v[1]];
  for(int i=0; i<6; i+=2) {
    a[v[i]] = a[v[i+2]];
    a[v[i+1]] = a[v[i+3]];

  }
  a[v[6]] = tmp1, a[v[7]] = tmp2;

  if(check()) {
    cout << "YES" << endl;
    return 0;
  }
  REP(i, 24) a[i] = b[i];
  reverse(ALL(v));
  tmp1 = a[v[0]], tmp2 = a[v[1]];
  for(int i=0; i<6; i+=2) {
    a[v[i]] = a[v[i+2]];
    a[v[i+1]] = a[v[i+3]];

  }
  a[v[6]] = tmp1, a[v[7]] = tmp2;

  if(check()) {
    cout << "YES" << endl;
    return 0;
  }
  REP(i, 24) a[i] = b[i];

  // 6
  v = {0, 1, 17, 19, 11, 10, 14, 12};
  tmp1 = a[v[0]], tmp2 = a[v[1]];
  for(int i=0; i<6; i+=2) {
    a[v[i]] = a[v[i+2]];
    a[v[i+1]] = a[v[i+3]];
  }
  a[v[6]] = tmp1, a[v[7]] = tmp2;
  if(check()) {
    cout << "YES" << endl;
    return 0;
  }
  REP(i, 24) a[i] = b[i];
  reverse(ALL(v));
  tmp1 = a[v[0]], tmp2 = a[v[1]];
  for(int i=0; i<6; i+=2) {
    a[v[i]] = a[v[i+2]];
    a[v[i+1]] = a[v[i+3]];
  }
  a[v[6]] = tmp1, a[v[7]] = tmp2;
  if(check()) {
    cout << "YES" << endl;
    return 0;
  }
  REP(i, 24) a[i] = b[i];

  cout << "NO" << endl;

  return 0;
}

yukicoder 595 登山

問題ページ
No.595 登山 - yukicoder

考えたこと

  1. ARC067 D - Walk and Teleportを思い出す
  2. 同じように貪欲にできないかなとか考える
  3. 間を開けて歯抜けの状態で先に進んだほうがいいことはなさそう
  4. ワープして戻るときは単調増加しているできるだけ先のに進んで戻るのが最善そう
  5. 右に隣接しているやつに進むパターンとワープして戻るパターンで良い方を貪欲に選ぶみたいなのを書く
  6. 落ちる
    ーーー解説を読むーーー
  7. 右に進む区画と左に進む区画の2パターンを持ってDPしないといけない
  8. 右に隣接してるやつに進むときにワープしたほうが良いパターンが抜けてたりして色々と雑なコードを書いていた
  9. 漸化式通り実装すると通る

f:id:ferin_tech:20171111135713j:plain

//#define __USE_MINGW_ANSI_STDIO 0
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
#define int ll
typedef vector<int> VI;
typedef vector<VI> VVI;
typedef vector<ll> VL;
typedef vector<VL> VVL;
typedef pair<int, int> PII;

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define ALL(x) x.begin(), x.end()
#define IN(a, b, x) (a<=x&&x<b)
#define MP make_pair
#define PB push_back
#ifdef int
const ll INF = (1LL<<60);
#else
const int INF = (1LL<<30);
#endif
const double PI = 3.14159265359;
const double EPS = 1e-12;
const int MOD = 1000000007;

template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); }
template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); }

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

int h[200010], dp[2][200010];
signed main(void)
{
  int n, p;
  cin >> n >> p;
  REP(i, n) cin >> h[i];

  REP(i, 2) REP(j, n) dp[i][j] = INF;
  dp[0][0] = 0;

  FOR(i, 1, n) {
    dp[0][i] = min(min(dp[0][i-1], dp[1][i-1])+p, dp[0][i-1] + max(0LL, h[i]-h[i-1]));
    dp[1][i] = min(min(dp[0][i-1], dp[1][i-1])+p, dp[1][i-1] + max(h[i-1]-h[i], 0LL));
  }

  // REP(i, 2) {
  //   REP(j, n) cout << dp[i][j] << " ";
  //   cout << endl;
  // }

  cout << min(dp[0][n-1], dp[1][n-1]) << endl;

  return 0;
}

ABC076 D - AtCoder Express

問題ページ
D: AtCoder Express - AtCoder Beginner Contest 076 | AtCoder

考えたこと

区間で場合分けして見ていくのを考える。サンプルを見ていると無限に場合分けがありそうで確実にバグらせるのでやめる。
現在の速度を持っておいて1秒ごとにシミュレーションしていき、速度を+1 or キープ or -1の中でできる速度を最大にするものを選択すればよさそうと思って実装する。このタイミングで速度をプラス(キープ)して次の速度までに下げきれるかを判定する。
サンプル4で引っかかって0.5秒ごとのシミュレーションに変更。もっと細かくしないといけないパターンが存在しないか考えたがなさそうだったのでこれで提出するとWA。
速度を変更するところで次の速度だけを見てるがそれだともっと先の区間に間に合わないパターンが存在する事に気づいた。制約を見返すとさらにもう一個Nが掛かっても何とかなりそうなので速度の更新パートにO(N)掛けることにして実装して投げたら通った。

実装面倒だなーと思ったけど考察したらそうでもなかった。

解法

現在の速度を持ちつつ、0.5秒ごとにシミュレーションして速度の更新を行う。速度の更新をするときにプラス(キープ)した後にマイナスをし続けても間に合わない区間が先に存在する時はプラス(キープ)が行えない。したがって現在可能な最大の速度を持ちつつ、これまでに走った最長の距離を求めていく。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
#define int ll
typedef vector<int> VI;
typedef vector<VI> VVI;
typedef vector<ll> VL;
typedef vector<VL> VVL;
typedef pair<int, int> PII;

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define ALL(x) x.begin(), x.end()
#define IN(a, b, x) (a<=x&&x<b)
#define MP make_pair
#define PB push_back
#ifdef int
const ll INF = (1LL<<60);
#else
const int INF = (1LL<<30);
#endif
const double PI = 3.14159265359;
const double EPS = 1e-12;
const int MOD = 1000000007;

template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); }
template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); }

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

int t[210], v[210];
signed main(void)
{
  int n;
  cin >> n;
  REP(i, n) cin >> t[i];
  REP(i, n) cin >> v[i];

  double ret = 0, sp = 0;
  REP(i, n) {
    for(double j=0; j<t[i]; j+=0.5) {
      bool flag1 = true, flag2 = true;
      double ti = t[i]-j-0.5;
      FOR(k, i+1, n) {
        ti += t[k];
        if(sp+0.5-ti > v[k+1]) flag1 = false;
        if(sp-v[k+1] > ti) flag2 = false;
      }
      if(sp+0.5 <= v[i] && flag1 && (sp+0.5-(t[i]-j-0.5)) <= v[i+1]) {
        ret += sp*0.5;
        ret += 0.125;
        sp += 0.5;
      } else if(sp - v[i+1] <= t[i]-j-0.5 && flag2) {
        ret += sp*0.5;
      } else {
        sp -= 0.5;
        ret += sp*0.5;
        ret += 0.125;
      }
    }
  }
  cout <<fixed << setprecision(15) << ret << endl;

  return 0;
}

ABC076 C - Dubious Document 2

問題ページ
C: Dubious Document 2 - AtCoder Beginner Contest 076 | AtCoder

解法

O(|S||T|)で愚直に探索していく。文字列S内に文字列Tが内包されることがありうるかをこの時点で判定し存在しなければ終了。文字列Tを当てはめるために変換する?以外は全部aに変換するべき。複数パターンTを当てはめるところがあれば最後の方においたほうが辞書順最小になる。したがって、Tを当てはめられるもっとも後ろのところをTに置き換え、残りの?をaにする。

追記 S = ?b??, T = abのときこのコードだとababを返すが正しくはabaaなのでこれは嘘解法(通っちゃったけど
候補を全部(高々50通り)持っておいてその中で辞書順最小のものを返すべき

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
#define int ll
typedef vector<int> VI;
typedef vector<VI> VVI;
typedef vector<ll> VL;
typedef vector<VL> VVL;
typedef pair<int, int> PII;

#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define ALL(x) x.begin(), x.end()
#define IN(a, b, x) (a<=x&&x<b)
#define MP make_pair
#define PB push_back
#ifdef int
const ll INF = (1LL<<60);
#else
const int INF = (1LL<<30);
#endif
const double PI = 3.14159265359;
const double EPS = 1e-12;
const int MOD = 1000000007;

template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); }
template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); }

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

signed main(void)
{
  string s,t;
  cin >> s >> t;
  bool ret = false;
  int idx = -1;
  REP(i, s.size()-t.size()+1) {
    bool flag = true;
    REP(j, t.size()) {
      if(s[i+j] == '?' || s[i+j] == t[j]) continue;
      flag = false;
      break;
    }
    if(flag) ret = true, idx = i;
  }
  if(!ret) {
    cout << "UNRESTORABLE" << endl;
    return 0;
  }
  REP(i, t.size()) {
    if(s[i+idx] == '?') s[i+idx] = t[i];
  }
  REP(i, s.size()) {
    if(s[i] == '?') s[i] = 'a';
  }
  cout << s << endl;

  return 0;
}