盆栽エンジニアリング日記

勉強したことをまとめるブログ

ABC167自分なりのまとめ

A- Registration

SとTの末尾以外を比較して一致していればOK

//競技プログラミング用のテンプレート
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <queue>
#include <string>
#include <math.h>
#include <stack>
#include <limits>

#define rep(i,n) for(int i=0, i##_len=(n); i<i##_len; ++i)
#define repr(i, n) for(int i = n - 1; i >= 0; i--)

using namespace std;
//エイリアス
using ll = long long int;
using p = pair<int, int>;
using pl = pair<ll, ll>;
using v = vector<int>;
using vd = vector<double>;
using vs = vector<string>;
using vl = vector<ll>;

//定数
const int intmax = numeric_limits<int>::max();
const ll llmax = numeric_limits<ll>::max();
const ll mod = 1e9 + 7;
const double pi = M_PI;

int main()
{
  string s, t;
  cin >> s >> t;
  rep(i, s.size()) {
    if(s[i] != t[i]) {
      cout << "No" << endl;
      return 0;
    }
  }
  cout << "Yes" << endl;
}

B-Easy Linear Programming

A+BがKよりも大きいならばKが答え
A+BがK以下ならばA+BからK - (A + B)を引いたものが答え

//競技プログラミング用のテンプレート
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <queue>
#include <string>
#include <math.h>
#include <stack>
#include <limits>

#define rep(i,n) for(int i=0, i##_len=(n); i<i##_len; ++i)
#define repr(i, n) for(int i = n - 1; i >= 0; i--)

using namespace std;
//エイリアス
using ll = long long int;
using p = pair<int, int>;
using pl = pair<ll, ll>;
using v = vector<int>;
using vd = vector<double>;
using vs = vector<string>;
using vl = vector<ll>;

//定数
const int intmax = numeric_limits<int>::max();
const ll llmax = numeric_limits<ll>::max();
const ll mod = 1e9 + 7;
const double pi = M_PI;

int main()
{
  ll a, b, c, k;
  cin >> a >> b >> c >> k;
  ll result = 0;
  if( a + b <= k ) {
    result += a;
    k -= a + b;
  }else{
    result += k;
    cout << result << endl;
    return 0;
  }
  result -= k;
  cout << result << endl;
}

C-Skill Up

久しぶりのBit全探索
ある参考書を購入する場合は1,購入しない場合を0とすると、参考書の購入パターンは12桁の2進数で表すことができる。
212 = 4096であるため、全パターンを探索することが可能。
コードが少し汚いけど許してください。

//競技プログラミング用のテンプレート
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <queue>
#include <string>
#include <math.h>
#include <stack>
#include <limits>

#define rep(i,n) for(int i=0, i##_len=(n); i<i##_len; ++i)
#define repr(i, n) for(int i = n - 1; i >= 0; i--)

using namespace std;
//エイリアス
using ll = long long int;
using p = pair<int, int>;
using pl = pair<ll, ll>;
using v = vector<int>;
using vd = vector<double>;
using vs = vector<string>;
using vl = vector<ll>;

//定数
const int intmax = numeric_limits<int>::max();
const ll llmax = numeric_limits<ll>::max();
const ll mod = 1e9 + 7;
const double pi = M_PI;

int main()
{
  int n, m, x;
  cin >> n >> m >> x;
  v c(n, 0);
  vector<vector<int>> a(n, vector<int>(m, 0));
  rep(i, n) {
    cin >> c[i];
    rep(j, m) {
      cin >> a[i][j];
    }
  }
  int result = intmax;
  for(int i = 1; i <= 4096; i++) {
    v alg(m, 0);
    int cost = 0;
    for(int j = 0; j < n; j++) {
      if(i & (1 << j)) {
        rep(k, m) {
          alg[k] += a[j][k];
        }
        cost += c[j];
      }
    }
    bool ok = true;
    rep(k, m) {
      if(alg[k] < x) {
        ok = false;
      }
    }
    if(ok) {
      result = min(result, cost);
    }
  }
  if (result == intmax) {
    cout << -1 << endl;
  }else{
    cout << result << endl;
  }
}

D-Teleporter

与えられた問題をグラフで考えてみると、全てのノードの出次数が1となっているグラフであることがわかる。
出次数とは、自分から出ていく辺の数です。
全ノードの出次数が1であるため、たかだかN回移動を繰り返すと、必ず訪問済みのノードに到達することがわかります。
一度訪問済みのノードに到達すると、あとはそのループを繰り返すだけになります。
最初のサンプルを例に取ると、

  • 1 -> 3 -> 4 -> 1

というルートで訪問済みの1に戻ることになります。
そして、以降はどれだけ移動を重ねても、その経路はこのループ上をぐるぐる回るだけになります。
この性質を用いると、K回の移動をシミュレーションする必要がなくなります。
具体的には、ループに入るまでに必要な移動数をK'、ループの長さをLとすると
(K - K') % L回、ループの先頭から移動するだけでよくなります。
これは、(K - K') / L回のループ回数は省略することができるためです。
Lは必ずN以下であるため、計算量も十分高速です。

//競技プログラミング用のテンプレート
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <queue>
#include <string>
#include <math.h>
#include <stack>
#include <limits>

#define rep(i,n) for(int i=0, i##_len=(n); i<i##_len; ++i)
#define repr(i, n) for(int i = n - 1; i >= 0; i--)

using namespace std;
//エイリアス
using ll = long long int;
using p = pair<int, int>;
using pl = pair<ll, ll>;
using v = vector<int>;
using vd = vector<double>;
using vs = vector<string>;
using vl = vector<ll>;

//定数
const int intmax = numeric_limits<int>::max();
const ll llmax = numeric_limits<ll>::max();
const ll mod = 1e9 + 7;
const double pi = M_PI;

int n;
ll k;
int a[2 * 100000];

pair<int, ll> calc(int start) {
  if(k == 0) return {start, 1};
  vector<bool> flag(n, false);
  flag[start] = true;
  int cnt = 0;
  while(1) {
    start = a[start] - 1;
    cnt++;
    if(k == cnt){
      k -= cnt;
      return {start, cnt};
    };
    if(flag[start]){
      k -= cnt;
      return {start, cnt};
    }else{
      flag[start] =true;
    }
  }
}

int main()
{
  cin >> n >> k;
  rep(i, n) cin >> a[i];
  auto stop = calc(0);
  if(stop.first != 0) {
    stop = calc(stop.first);
  }
  ll rest = k % stop.second;
  int result = stop.first;
  rep(i, rest) {
    result = a[result] - 1;
  }
  cout << result + 1 << endl;
}

E-Colorful Blocks

先週に続いて、今週も自力で解くことができました!
まず最初に、素朴なケースであるK=0の場合を考えてみます。
この場合、最初のブロックにはM色を選ぶことができ、あとのブロックにはそれぞれM-1色を選ぶことができます。
これは、1つ前のブロックと同じ色は選ぶ事ができないためです。
よって、ありえるパターン数はM * (M - 1)N - 1個となります。
次に、このケースを利用してK=1の場合を考えてみましょう。
K=1の場合、すべての組の中で1つだけ色が同じになる組を許します。
今、組はN-1個あり、この中から1つを選ぶ組み合わせの数はN-1 C 1個あります。
そして、そのような組を1つ選ぶとありえるパターン数はM * (M - 1)N - 1 / (M - 1)となります。
これは、1つのブロックの色の選択肢がなくなるためです。
よって、K=1の場合、(M * (M - 1)N - 1 / (M - 1)) * (N - 1 C 1)となります。
K=2以降の場合についても、同様の考え方で求めていく事ができます。
注意点としてはM = 2の場合にはK = N - 1以外は可能な塗り方が存在しないことと、
modの世界で計算をしていくので逆元を求める必要があることです。

//競技プログラミング用のテンプレート
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <queue>
#include <string>
#include <math.h>
#include <stack>
#include <limits>

#define rep(i,n) for(int i=0, i##_len=(n); i<i##_len; ++i)
#define repr(i, n) for(int i = n - 1; i >= 0; i--)

using namespace std;
//エイリアス
using ll = long long int;
using p = pair<int, int>;
using pl = pair<ll, ll>;
using v = vector<int>;
using vd = vector<double>;
using vs = vector<string>;
using vl = vector<ll>;

//定数
const int intmax = numeric_limits<int>::max();
const ll llmax = numeric_limits<ll>::max();
const ll mod = 998244353;
const double pi = M_PI;


template <typename number>
pair<number, number> extended_euclid(number a, number b)
{
  number r0, r1, a0, a1, b0, b1;
  r0 = a; r1 = b;
  a0 = 1; a1 = 0;
  b0 = 0; b1 = 1;
  while (r1>0) {
    number q1 = r0 / r1;
    number r2 = r0 % r1;
    number a2 = a0 - q1 * a1;
    number b2 = b0 - q1 * b1;
    r0 = r1 ; r1 = r2;
    a0 = a1 ; a1 = a2;
    b0 = b1 ; b1 = b2;
  }
  number c = r0;
  a = a0; //x
  b = b0; //y
  return  {a, c};
}

template  <typename number>
number inverse_N(number a, number N)
{
  pair<number, number> result = extended_euclid(a, N);
  if(result.first < 0)
  {
    result.first = result.first + N;
  }
  return result.first;
}

ll n, m, k;

ll calc() {
  ll result = m;
  rep(i, n - 1) {
    result = (result * (m - 1)) % mod;
  }
  return result;
}

int main()
{
  cin >> n >> m >> k;
  if(m == 1) {
    if(k == n - 1){
      cout << 1 << endl;
    }else{
      cout << 0 << endl;
    }
    return 0;
  }
  ll base = calc();
  ll minv = inverse_N(m - 1, mod);
  ll numerator = n - 1;
  ll denominator = 1;
  ll cand = numerator;
  ll result = base;
  for(int i = 1; i <= k; i++) {
    ll temp = base * minv;
    temp %= mod;
    temp *= cand;
    temp %= mod;
    result += temp;
    result %= mod;
    //分母の更新
    numerator = (numerator * (n - 1 - i)) % mod;
    //分子の更新
    denominator = (denominator * (i + 1)) % mod;
    //組み合わせの更新
    cand = numerator * inverse_N(denominator, mod);
    cand %= mod;
    //減る候補を更新
    minv = minv * inverse_N(m - 1, mod);
    minv %= mod;
  }
  cout << result << endl;
}