ferinの競プロ帳

競プロについてのメモ

ABC023を解いてみた

abc023.contest.atcoder.jp

A問題

 string型で入力を受けとり1桁ずつに区分けし加算していきました。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

#define MOD 1000000007

int main(void)
{
  int x, ans = 0;
  string str;
  cin >> str;

  for(int i=0; i<str.size(); i++) {
    ans += (int(str[i])-48); //文字コード
  }

  cout << ans << endl;

  return 0;
}

B問題

 手順の回数は (n-1)/2 回となります。問題文の処理をこの回数分行い、できた文字列と与えられた文字列を比較しました。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

#define MOD 1000000007

int main(void)
{
  int n;
  string s, comp = "b";

  cin >> n;
  cin >> s;

  for(int i=1; i<=(n-1)/2; i++) {
    if (i % 3 == 1) comp = "a" + comp + "c";
    else if(i % 3 == 2) comp = "c" + comp + "a";
    else comp = "b" + comp + "b";
  }

  if(s == comp) cout << (n-1)/2 << endl;
  else cout << -1 << endl;

  return 0;
}

C問題

 部分点解法しか思い浮かばなかったので解説を見て実装しました。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

#define MOD 1000000007
#define MAX_N 100000

int main(void)
{
  int r, c, k;
  cin >> r >> c >> k;
  int n;
  cin >> n;

  vector<int> y(n), x(n), countrow(r, 0), countcol(c, 0);
  for(int i=0; i<n; i++) {
    cin >> y[i] >> x[i];
    y[i]--; x[i]--;
    //列、行ごとの飴の数
    //countrow[i] = j i行目にj個の飴
    countrow[y[i]]++; countcol[x[i]]++;
  }

  //特定の飴の個数の行、列の数
  //numrow[i] = j 飴の数がi個の行がj個
  vector<ll> numrow(n+1, 0), numcol(n+1, 0);
  for(int i=0; i<r; i++) numrow[countrow[i]]++;
  for(int i=0; i<c; i++) numcol[countcol[i]]++;

  ll sum = 0;
  for(int i=0; i<=k; i++) sum += numrow[i]*numcol[k-i];
  for(int i=0; i<n; i++) {
    //飴がある場所を起点としていて合計がk, k+1のとき
    if(countrow[y[i]] + countcol[x[i]] == k) sum--;
    else if(countrow[y[i]] + countcol[x[i]] == k+1) sum++;
  }

  cout << sum << endl;

  return 0;
}

D問題

 思い浮かばなかったので解説を見て実装しました。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

#define MOD 1000000007

int main(void)
{
  int n;
  cin >> n;
  vector<ll> h(n), s(n);
  for(int i=0; i<n; i++) cin >> h[i] >> s[i];

  ll hmax = *max_element(h.begin(), h.end()), smax = *max_element(s.begin(), s.end());
  //一番大きいxとしてありえるのはmax(h)+max(s)*n
  ll left=0, right=hmax+smax*n, mid=(left+right)/2;
  bool flag = true;
  vector<ll> t(n);

  //二分探索
  while( right-left > 1)
  {
    flag = true; mid=(left+right)/2;
    // midのとき条件を満たすかを確かめる
    for(int i=0; i<n; i++) {
      if(mid < h[i]) t[i] = -1;
      else t[i] = (mid-h[i])/s[i];
    }
    sort(t.begin(), t.end());
    for(int i=0; i<n; i++) {
      //貪欲的に考えていって成り立たないt[i]が存在するなら条件は満たさない
      if(t[i] < i) {
        flag = false;
        break;
      }
    }

    //条件を満たすなら
    if(flag) {right = mid;}
    //条件を満たさないなら
    else {left = mid;}
  }

  cout << right << endl;
  return 0;
}

ABC040を解いてみた

abc040.contest.atcoder.jp

A問題

 n-1とx-nのうち小さい方を出力しました。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

#define MOD 1000000007

int main(void)
{
  int n = 0, x = 0;
  cin >> n >> x;

  if(x-1 >= n-x) cout << n-x << endl;
  else cout << x-1 << endl;
  
  return 0;
}

B問題

 1からsqrt(n)まで四角形の1辺を探索し、もう1辺を求めコストを算出しました。そのうち最小となるコストを出力しました。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

#define MOD 1000000007

int main(void)
{
  int n, j, cost, mincost = 300000;
  cin >> n;

  for(int i = 1; i <= sqrt(n); i++)
  {
    j = n/i;
    cost = abs(i-j) + n - i*j;
    mincost = min(mincost, cost);
  }

  cout << mincost << endl;
  return 0;
}

C問題

 dp[i] = (i番目まで移動するのにかかる最小コスト) という変数で、DPを行って求めました。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

#define MOD 1000000007
#define MAX_N 100000

int main(void)
{
  int n, a[MAX_N], dp[MAX_N];

  cin >> n;
  for(int i=0; i<n; ++i)
    cin >> a[i];

  dp[0] = 0;
  dp[1] = abs(a[1]-a[0]);
  for(int i=2; i<n; ++i)
  {
    dp[i] = min(dp[i-1] + abs(a[i]-a[i-1]), dp[i-2] + abs(a[i]-a[i-2]));
  }

  cout << dp[n-1] << endl;

  return 0;
}

D問題

 Union-Find木を知らなかったので蟻本で勉強して出直してきます…

ABC022を解いてみた

abc022.contest.atcoder.jp

A問題

 N日目のとき、適正体重かどうかを判定しました。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

#define MOD 1000000007

int main(void)
{
  int n=0, s=0, t=0, w=0, a[1000], ans=0;
  cin >> n >> s >> t;
  cin >> w;
  for(int i=1; i<n; ++i) cin >> a[i];

  if(s <= w && w <= t) ++ans;
  for(int i=1; i<n; ++i)
  {
    w += a[i];
    if(s <= w && w <= t) ++ans;
  }

  cout << ans << endl;

  return 0;
}

B問題

 たどった花の種類別に数を数えて2個以上になった種類は、その数-1が受粉するという方針で実装しました。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

#define MOD 1000000007
#define MAX_N 100000

int main(void)
{
  int n=0, a=0, lis[MAX_N]={}, ans=0;
  cin >> n;
  for(int i=0; i<n; ++i)
  {
    cin >> a;
    lis[a-1] += 1;
  }

  for(int i=0; i<MAX_N; ++i)
  {
    //if(i < 30) cout << "lis: " << lis[i] << endl;
    if(lis[i] >= 2) ans += (lis[i]-1);
  }

  cout << ans << endl;
  return 0;
}

 

C問題

 もっとも短い経路の閉路を探すアルゴリズムが思いつかなかったので解説見て実装しました。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

#define MOD 1000000007
#define INF 999999999
#define MAX_N 300

int main(void)
{
  //lis1が1に隣接する頂点へのコスト
  int n, m, u, v, l, d[MAX_N][MAX_N]={}, lis1[MAX_N]={}, ret = 0, minret = INF;
  //lisが1に隣接する頂点番号のリスト
  vector<int> lis;
  bool flag = true;

  // dの初期化
  fill( d[0], d[MAX_N], INF);
  for(int i=1; i<MAX_N; ++i) d[i][i] = 0;

  //入力
  cin >> n >> m;
  for(int i=0; i<m; ++i)
  {
    cin >> u >> v >> l;
    if(u != 1 && v != 1) {d[u-1][v-1] = l; d[v-1][u-1] = l;}
    else if(u == 1) {lis1[v-1] = l; lis.push_back(v);}
    else {lis1[u-1] = l; lis.push_back(u);}
  }

  //ワーシャルフロイド
  // d[i][j] で頂点iから頂点jへの最短距離
  for(int k=0; k<n; ++k) {
    for(int i=0; i<n; ++i) {
      for(int j=0; j<n; ++j) {
        d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
      }
    }
  }

  // lis の中から2つを選択
  for(int i=0; i < lis.size()-1; ++i) {
    for(int j=i+1; j < lis.size(); ++j) {
      ret = lis1[lis[i]-1] + lis1[lis[j]-1] + d[lis[i]-1][lis[j]-1];
      //cout << "lis[i]:" << lis[i]-1 << " lis[j]:" << lis[j]-1 << " lis1[lis[i]]" << lis1[lis[i]-1] << " lis1[lis[j]]" << lis1[lis[j]-1] << " d" << d[lis[i]-1][lis[j]-1] << " ret:" << ret << endl;
      minret = min(minret, ret);
    }
  }

  //たどり着けるパターンがなかったら-1を出力
  if(minret == INF) minret = -1;
  cout << minret << endl;

  return 0;
}

D問題

 回転行列を使ってやるのかなとか考えてても実装方法が思い浮かばなかったので、解説見て実装しました。N点の重心と最遠点の比率による方法をつかいました。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

#define MOD 1000000007
#define MAX_N 100000

int main(void)
{
  int n, ax[MAX_N], ay[MAX_N], bx[MAX_N], by[MAX_N];
  double agx=0.0, agy=0.0, bgx=0.0, bgy=0.0, adist=0.0, bdist=0.0, maxadist=0.0, maxbdist=0.0;

  cin >> n;
  for(int i=0; i<n; i++) cin >> ax[i] >> ay[i];
  for(int i=0; i<n; i++) cin >> bx[i] >> by[i];

  for(int i=0; i<n; i++) {
    agx += ax[i]; agy += ay[i];
    bgx += bx[i]; bgy += by[i];
  }
  agx /= n; agy /= n;
  bgx /= n; bgy /= n;

  //cout << agx << " " << agy << " " << bgx << " " << bgy << endl;

  for(int i=0; i<n; i++) {
    adist = pow(ax[i] - agx, 2) + pow(ay[i] - agy, 2);
    maxadist = max(maxadist, adist);
    bdist = pow(bx[i] - bgx, 2) + pow(by[i] - bgy, 2);
    maxbdist = max(maxbdist, bdist);
  }

  //cout << "maxa:" << maxadist << " maxb:" << maxbdist << endl;
  cout << setprecision(12) << sqrt(maxbdist)/sqrt(maxadist) << endl;

  return 0;
}

ABC041を解いてみた

A問題

 入力をString型で受けとりS[i-1]を出力しました。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

#define MOD 1000000007

int main(void)
{
  string S;
 int i;
 cin >> S;
 cin >> i;
 cout << S[i-1] << endl;
 return 0;
}

B問題

 直方体の体積の計算自体はA*B*Cで求められる。変数はlong long型で確保しておきオーバーフローしないように掛けるたびに余剰の計算を行いました。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

#define MOD 1000000007

int main(void)
{
  ll a, b, c, ans;
  scanf("%lld %lld %lld", &a, &b, &c);

  ans = (((a*b)%MOD)*c)%MOD;
  printf("%lld\n", ans);

  return 0;
}

C問題

 vector< pair< int, int > > (身長と出席番号のペア)を作成し身長をもとにソートを実行、そしてその順番で出席番号を出力しました。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

#define MOD 1000000007

int main(void)
{
  int n = 0, a = 0;

  scanf("%d", &n);
  vector< pair<int, int> > height(n);

  for(int i = 0; i < n; i++)
  {
    scanf("%d", &a);
    height[i] = make_pair(a, i);
  }

  sort(height.begin(), height.end(), greater<pair<int,int> >());

  for(int i = 0; i < n; i++)
    printf("%d\n", height[i].second+1);

  return 0;
}

D問題

 満点解法のトポロジカルソートを用いる方法の計算が理解できなかったのでとりあえず全探索の部分点解法について。next_permutationを使って全パターンについて探索し、各パターンについてxがyより先に出ればそのパターンは条件を満たしていると考えられます。満点解法については理解できたら書きます…

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

#define MOD 1000000007

int main(void)
{
  int n = 0, m = 0, x[120], y[120], lis[16][16];
  scanf("%d %d", &n, &m);
  for(int i = 0; i < m; i++)
  {
    scanf("%d %d", &x[i], &y[i]);
  }

  vector<int> data;
  for(int i = 1; i <= n; i++) {data.push_back(i);}

  ll ans = 0;
  bool flag = true;
  do
  {
    for(int i = 0; i < m; ++i)
    {
      for(int j = 0; j < n; ++j)
      {
        //xのあとにyが出ればtrueに yのあとにxが出ればfalseになる
        if(data[j] == x[i]) flag = false;
        else if(data[j] == y[i]) flag = true;
      }
      if(!flag) break;
    }
    if(flag) ans++;
  }while(next_permutation(data.begin(), data.end()));

  printf("%d\n", ans);

  return 0;
}

ABC042を解いてみた

abc042.contest.atcoder.jp

A問題

 入力をソートして5, 5, 7になっていればYES, なっていなければNOを出力しました。

a = [int(i) for i in input().split()]
a.sort()

if a[0] == 5 and a[1] == 5 and a[2] == 7:
    print("YES")
else:
    print("NO")

B問題

 ソート関数で辞書順に並べて出力しました。

n, l = map(int, input().split())
s = [[i for i in input()] for j in range(n)]

s.sort()

for i in range(n):
    s[i] = "".join(s[i])
s = "".join(s)

print(s)

C問題

 桁を増やす必要があるかどうか、Nをすでに超えているかどうかなどの場合分けをおこない1桁ずつ決めていきました。1~10Nまで全探索すればよいなんてやり方に気がつかないなんて…という感じです。

n, k = map(int, input().split())
d = [int(i) for i in input().split()]
ablenum = [int(i) for i in range(10)]
lis_n = str(n)
lis_n = list(lis_n)

# 使用可能な数一覧
for i in d:
    ablenum.remove(i)

flag_bigdigit = -1

# max(ablenum)より大きい桁があるかどうか
for i in range(len(lis_n)):
    _i = len(lis_n) - i - 1
    if(int(lis_n[_i]) > max(ablenum)):
        flag_bigdigit = _i
        break

#  max(ablenum)より大きい桁があれば
if(flag_bigdigit != -1):
    flag_increaseDights = 1
    #  桁を増やす必要があるかどうか確認
    for i in range(flag_bigdigit-1, 0):
        # nの桁より大きい数字がablenumに存在していれば桁を増やす必要はない
        if(int(lis_n[i]) < max(ablenum)):
            flag_increaseDights = 0
            break
else:
    flag_increaseDights = 0

ans = ""
# 桁を増やす必要があるならば
if(flag_increaseDights == 1):
    for i in range(len(lis_n)+1):
        # 一桁目は0以外の最も小さな数
        if(i == 0):
            mnum = min(ablenum)
            if(mnum == 0):
                mnum = ablenum[1]
            ans += str(mnum)
        # 2桁目以降は最も小さい数
        else:
            mnum = min(ablenum)
            ans += str(mnum)
# 桁を増やす必要がなければ、各桁の数字以上の最も小さい数を使う
else:
    flag_over = 0
    for i in range(len(lis_n)):
        if(flag_over == 0):
            mnum = min(ablenum)
            # その桁以上の数になるまで大きくする
            j = 0
            while mnum < int(lis_n[i]):
                j += 1
                mnum = ablenum[j]
            # n以上となることが確定すれば
            if(mnum > int(lis_n[i])):
                flag_over = 1
            ans += str(mnum)
        # n以上となることが確定していれば使える中で最も小さい数を使う
        else:
            mnum = min(ablenum)
            ans += str(mnum)

print(ans)

D問題

 最短経路数はコンビネーションを使って計算できるので、nCr(mod m)を高速で求める方法を使って実装しました。逆元を予め求めておくことでコンビネーションはO(1)で計算できます。フェルマーの小定理から逆元はmod mのときx^m-2で求められます。二分累乗法でx^nはO(logn)で計算できるので逆元の列はO(nlogn)で計算可能です。
 Pythonでh=w=10^5のケースを試してみたところ、明らかに2秒以内に計算できていなかったのでいろいろ試してみたんですが実行時間を減らすことができずC++で書き直しました。オーバーフローを気にしなくてもよかったりPythonは好きですが、競プロで使う分にはC++のほうがよさそうだと思えてきました。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

#define MOD 1000000007

ll fact[200000], ifact[200000], dp[100000];
int h, w, a, b;

//二分累乗法
ll binpow(ll x, ll e)
{
  ll a = 1;
  ll p = x;

  while(e > 0)
  {
    if(e%2 == 0) {p = (p*p) % MOD; e /= 2;}
    else {a = (a*p) % MOD; e--;}
  }

  return a;
}

int main(void)
{
  fact[0] = 1;
  ifact[0] = 1;
  scanf("%d %d %d %d", &h, &w, &a, &b);
  //逆元の列を計算
  for(int i = 1; i < h+w; i++)
  {
    fact[i] = fact[i-1] * i % MOD;
    ifact[i] = binpow(fact[i], MOD-2);
  }

  // (b-1, 0) から (b-1, h-a-1) までの経路数を計算
  for(int i = 0; i < h-a; i++)
      dp[i] = ((fact[b-1+i]*ifact[b-1]%MOD)*ifact[i]%MOD);

  // (b-1, 0) から (b-1, h-a-1) から右下までの経路数を計算
  ll ans = 0LL;
  for(int i = 0; i < h-a; i++)
      ans = (ans + ((dp[i]*fact[h+w-b-i-2]%MOD)*ifact[h-i-1]%MOD)*ifact[w-b-1]%MOD) % MOD;

  printf("%lld\n", ans);
  return 0;
}

ABC048に参加してみた

abc048.contest.atcoder.jp

A問題

 スライスを使って先頭の文字だけを取得して連結しました。

s = [i for i in input().split()]

ans = s[0][0:1] + s[1][0:1] + s[2][0:1]
print(ans)

B問題

 ans = b/x - a/x で求まります。aがxの倍数の時だけans++とすれば大丈夫(なはず)です。
 指数表記をint()で整数に直そうとしたんですが、下のように値が変化してしまい悩んでました。

>>> 1000000000000000000/3
3.333333333333333e+17
>>> int(1000000000000000000/3)
333333333333333312

 最終的に、C++で書いたら問題なさそうだったのでc++で提出しました。

C問題

 配列aを左から2つずつ区切っていったときにその2つのうち左側を優先的に減らす理由はないため、右側を優先的に減らしていく貪欲法で実装しました。

n, x = map(int, input().split())
a = [int(i) for i in input().split()]

count = 0
for i in range(n):
    s = sum(a[i:i+2])
    if s > x:
        if (s - x > a[i+1]):
            a[i+1] = 0
            a[i] -= (s - x - a[i+1])
        else:
            a[i+1] -= sum(a[i:i+2]) - x
        count += s - x

print(count)

D問題

 適当に入力例を紙に書いて試していたところ、ab???baのように真ん中に挟まれているところがあったとしても関係なさそうだと思いつき、両端の文字と文字列の長さが偶数か奇数かで判断すればよさそうだと思い浮かび、ちゃんと証明できていたわけではありませんがとりあえず提出したらACでした。

s = input()

if s[0:1] == s[len(s)-1:len(s)]:
    if len(s) % 2 == 1:
        print("Second")
    else:
        print("First")
else:
    if len(s) % 2 == 1:
        print("First")
    else:
        print("Second")

雑感

 Bで悩んでいた時間がすごいもったいなかったなと思いました。苦手なDPが出なかったこともあって4完できてよかったです。

ABC043を解いてみた

abc043.contest.atcoder.jp

A問題

 1からNまでの和をn(n+1)/2で求めます。

n = int(input())
print(int(n*(n+1)/2))

B問題

 sの長さは10以下と非常に短いため、条件に従って答えとなる文字列を操作していけばよさそうです。この方針で実装しました。

s = list(input())

ans = ""
for i in s:
    # 0を連結
    if i == "0":
        ans = ans + "0"
    # 1を連結
    elif i == "1":
        ans = ans + "1"
    # 一番右側の文字を削除
    elif i == "B":
        ans = ans[:len(ans)-1]

print(ans)

C問題

 NとAの範囲が狭く全探索で問題なさそうです。置き換える後の数字はAの制約である-100から100の範囲で必ず収まります。置き換える後の数字を全て、全てのNに対して試せばよさそうです。O(AN)≒O(20000)程度で収まるため実行時間も問題なさそうです。
 最初、range(-100, 100)としていて範囲に100が入っておらず一回WAを出してしまったので気をつけたいです。

n = int(input())
a = [int(i) for i in input().split()]

mincost = 10000000
for i in range(-100, 101):
    cost = 0
    for j in range(n):
        cost += (a[j]-i) ** 2
    mincost = min(cost, mincost)

print(mincost)

D問題

 最初、しゃくとり法を使う方針で考えて提出しましたがWAを出してしまい考え直すと、長さが3の部分文字列がアンバランスでないときにその部分文字列を含む長さが4の部分文字列がアンバランスであることはありえません。そこで文字列を3文字ずつに区切っていきアンバランスであるかを確かめていけばよさそうです。この方針で実装したところWA… コーナーケースを考えると入力sが"aa","ff"といった同じ文字2文字の場合があり、この入力sはアンバランスな部分文字列となりますが、3文字以上の文字列に対してのみ確認を行っていたため判定できていませんでした。修正を行って提出してAC!

import sys
s = input()

# 同じ文字2文字はアンバランス
if len(s) == 2 and s[0:1] == s[1:2]:
    print(1, 2)
    sys.exit()

for i in range(len(s)-2):
    #3文字で区切る
    temp = s[i:i+3]
    #アンバランスな部分文字列が存在すれば
    if temp[0:1] == temp[1:2] or temp[1:2] == temp[2:3] or temp[2:3] == temp[0:1]:
        print(i+1, i+3)
        sys.exit()

print(-1, -1)

雑感

 点数が低めのやさしめな問題だったのもあって自力で4完とれました。今日この後あるABCも頑張りたいです。