ferinの競プロ帳

競プロについてのメモ

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;
}

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;
}

AGC005 C - Tree Restoring

問題ページ
C: Tree Restoring - AtCoder Grand Contest 005 | AtCoder

解法

まず、max(a[i]) = Xの距離のグラフをつくることを考える。このグラフをつくるのに
* Xが偶数のとき
距離がX/2の頂点が1個、X/2+1~Xまでの頂点が2個ずつ必要
* Xが奇数のとき
距離がceil(X/2)+1~Xの頂点が2個ずつ必要
となる。このグラフに頂点を付け加えていくことを考えると
* Xが偶数のとき
距離がX/2+1~Xまでの頂点は付け加えられる
* Xが奇数のとき
距離がceil(X/2)+1~Xの頂点は付け加えられる となる。 f:id:ferin_tech:20171016225346p: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 int INF = (1LL<<30);
#else
const ll INF = (1LL<<60);
#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 a[105], cnt[105];
signed main(void)
{
  int n;
  cin >> n;
  int ma = 0;
  REP(i, n) {
    int a;
    cin >> a;
    cnt[a]++;
    chmax(ma, a);
  }

  if(ma%2==0) {
    for(int i=ma; i>=0; --i) {
      if(i > ma/2) {
        if(cnt[i] < 2) {
          cout << "Impossible" << endl;
          return 0;
        }
      } else if(i == ma/2) {
        if(cnt[i] != 1) {
          cout << "Impossible" << endl;
          return 0;
        }
      } else {
        if(cnt[i]) {
          cout << "Impossible" << endl;
          return 0;
        }
      }
    }
  } else {
    for(int i=ma; i>=0; --i) {
      if(i > ma/2+1) {
        if(cnt[i] < 2) {
          cout << "Impossible" << endl;
          return 0;
        }
      } else if(i == ma/2+1) {
        if(cnt[i] != 2) {
          cout << "Impossible" << endl;
          return 0;
        }
      } else {
        if(cnt[i]) {
          cout << "Impossible" << endl;
          return 0;
        }
      }
    }
  }
  cout << "Possible" << endl;

  return 0;
}

ARC069 E - Frequency

問題ページ
E: Frequency - AtCoder Regular Contest 069 | AtCoder

解法

辞書順最小なのでその時点で追加できる最小のものを追加していくようにする。

0 1 2 3 4 5 6 7 8 9
1 2 1 3 2 4 2 5 8 1 -> 8が3(=(8-5)*(8以上の数の個数))回追加 (最大の石の数は8)
1 2 1 3 2 4 2 5 5 1 -> 7が2(=(5-4)*(5以上の数の個数))回追加 (最大の石の数は5)
1 2 1 3 2 4 2 4 4 1 -> 5が3回追加 (最大の石の数は4)
1 2 1 3 2 3 2 3 3 1 -> 3が4回追加 (最大の石の数は3)
1 2 1 2 2 2 2 2 2 1 -> 1が7回追加 (最大の石の数は2)
1 1 1 1 1 1 1 1 1 1 -> 0が10回追加(最大の石の数は1)
0 0 0 0 0 0 0 0 0 0

a[i]が取りうる値を昇順に並べたものをX_iとする。X_i以上で最も左に存在するindexが(X_i以上のa[i]の個数)*(X_i-X_{i-1})回追加される。したがってX_i以上の個数とX_i以上で最も左にあるindexを保持しておけば計算できる。

//#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 int INF = (1LL<<30);
#else
const ll INF = (1LL<<60);
#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 a[100010], cnt[100010], l[100010], ans[100010];

// xx, yy, zzにそれぞれ座圧する元の配列を突っ込むとx, y, zが座圧された配列で返ってくる
// zip[座圧前] = 座圧後、unzip[座圧後] = 座圧前
VL xx, x, unzip(100010);
unordered_map<int, int> zip;
void compress() {
  x = xx;
  sort(ALL(xx));
  xx.erase(unique(ALL(xx)), xx.end());
  REP(i, xx.size()) {zip[xx[i]] = i; unzip[i] = xx[i];}
  REP(i, x.size()) x[i] = zip[x[i]];
}

signed main(void)
{
  int n;
  cin >> n;
  REP(i, n) {
    cin >> a[i];
    xx.PB(a[i]);
  }
  compress();

  memset(l, -1, sizeof(l));
  int ma = 0;
  REP(i, n) {
    chmax(ma, (int)x[i]);
    cnt[x[i]]++;
    if(l[x[i]] == -1) l[x[i]] = i;
  }
  for(int i=ma; i>=1; --i) cnt[i-1] += cnt[i];
  int mi = INF;
  for(int i=ma; i>=0; --i) {
    chmin(mi, l[i]);
    l[i] = mi;
  }
  REP(i, ma+1) {
    int tmp = i==0 ? xx[i] : xx[i] - xx[i-1];
    ans[l[i]] += cnt[i] * tmp;
  }

  REP(i, n) cout << ans[i] << endl;

  return 0;
}

SRM625 div1 easy PalindromePermutations

解法

まず、回文が存在する条件として
* それぞれの文字種の個数が全て偶数
* 一つの文字種だけ奇数でそれ以外は偶数
のいずれかの条件を満たす必要がある。文字種ごとに文字列内の個数を数え、奇数のものが2個以上あれば存在しないので0を出力する。
cnt[i] = 文字種iの個数とすると、文字列を並べ替えるパターン数は (|S|!)/(cnt[0]!cnt[1]!cnt[25]!) となる。回文の数は (|S|/2)! / ((cnt[0]/2)!(cnt[0]/2)!(cnt[0]/2)!) なので計算すればできる。オーバーフローに注意。

#include <bits/stdc++.h>

using namespace std;
typedef long long 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
const int INF = (1LL<<30);
const ll LLINF = (1LL<<60);
const double PI = 3.14159265359;
const double EPS = 1e-12;
const int MOD = 1000000007;
//#define int ll

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 cnt[30];

class PalindromePermutations {
   public:
   double palindromeProbability(string word)
  {
    memset(cnt, 0, sizeof(cnt));
    for(char c: word) cnt[c-'a']++;
    bool ng = false;
    int sum = 0;
    REP(i, 26) {
      if(!ng && cnt[i]%2) ng = true;
      else if(ng && cnt[i]%2) return 0;
      sum += cnt[i];
    }
    double ret = 1;
    FOR(i, (sum/2)+1, sum+1) {
      ret /= i;
    }
    REP(i, 26) {
      FOR(j, cnt[i]/2+1, cnt[i]+1) {
        ret *= j;
      }
    }
    return ret;
  }
};