おはやし日記

テーマ……バイク←プログラミング←旅

AtCoder ABC170 反省会 [A,B,C,D完, E追記]

こんにちは。ABC170でした。

↑意気込み

↑謎の予言

(追記:Eも解けたので良ければ見ていってください

o-treetree.hatenablog.com

A

1:37で提出

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

int main(){
  vector<int> x(5);
  for(int i=0; i<5;i++)cin>>x[i];
  for(int i=0; i<5; i++){
    if(x[i]==0){
      cout<<i+1<<endl;
      return 0;
    }
  }
}

これ、$x = \{0,0,0,0,0\}$ みたいなのって制約でハジけるんすかね、まあええわ

(追記:問題文の「変数 $x_i$ には整数 $i$ が代入されていました。」を誤読してた。操作前 $x = \{1,2,3,4,5\}$ か。)

B

与えられた変数 $X,Y$ がつるかめ算の(動物数, 足の数)としてありえる値かどうか判定するという問題

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

int main(){
  int x,y;cin>>x>>y;
  for(int turu = 0; turu<=100; turu++){
    for(int kame = 0; turu+kame<=100; kame++){
      if(turu+kame==x && 2*turu+4*kame==y){
        cout<<"Yes\n";
        return 0;
      }
    }
  }
  cout<<"No\n";
}

冒頭の予言により全探索する覚悟ができていたので全探索しました。

C

入力 $X$ に最も近い整数を、数列 $p$ 以外の整数から選ぶ問題

x = 6
p = {4 7 10 6 5}

ans = 8
i 1 2 3 4 5 6 7 8 9 10
選択可能 o o o x x x x o o x

選択可能な中で6に一番近いのは8。

(等距離の数字があれば小さい方を選択(↑で4も選択可能だったら4を出力))

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

int main(){
  map<int, int> mp;
  int X,N;cin>>X>>N;
  for(int i=0; i<N; i++){
    int f;cin>>f;mp[f]++;
  }
  int i,j;
  i=j=X;
  while(1){
    if(mp.find(i)==mp.end()){
      cout<<i<<endl;
      break;
    }
    if(mp.find(j)==mp.end()){
      cout<<j<<endl;
      break;
    }
    i--;j++;
  }
}

選択不可の数をmapで管理したけど、ふつうにvector<bool>でよかったな

探索を $i=j=X$ から初めて、$i$ は小さく、$j$ は大きくして見ていく

D

数列Aの各A[i]について、Aの他の要素でA[i]を割り切るものがないのをカウントする

#include <bits/stdc++.h>
using namespace std;
#define all(v) (v).begin(), (v).end()

long long divisor(long long n, vector<long long> &d){
  long long cnt = 0;
  for(long long i=1; i*i<=n; i++){
    if(n%i==0){
      d.push_back(i);
      if(i*i!=n){
        d.push_back(n/i);
      }
      cnt++;
    }
  }
  return cnt;
}

int main(){
  int N;cin>>N;
  vector<int> A(N);
  map<int,int> mp;
  for(int i=0; i<N; i++){
    cin>>A[i];
    mp[A[i]]++;
  }
  sort(all(A), greater<int>()); //ここ要らなかった

  int cnt = N;
  for(int i=0; i<N; i++){
    int s = A[i];

    if(mp[s]>1){
      cnt--;
      continue;
    }

    vector<long long> d;
    divisor(s,d);

    for(auto x: d){
      if(x==s)continue;
      if(mp.find(x)!=mp.end()){
        cnt--;
        break;
      }
    }
  }

  cout<<cnt<<endl;
}

数列Aを、配列の他にmapでも保持。divisor(n,d)は $n$ の約数を配列 d に入れる関数。

  • カウントを N にしておく
  • A[i]が2つ以上あったらカウントから外す(cnt--)。
  • A[i]の約数リストdを見て、約数 $x$ がA[i]自身だったら無視して
    • その他の数列Aの要素(map保持)に $x$ が入っていたらカウントから外す。

まとめ

DまでAC出たので満足しました。

Eはなんか解ける気せんし。

(追記:Eもなんかいけるみたいなことtwitterで見るし後でやってみます)

自作デバッグ関数の覚書き

プログラム書いていて、途中結果を出力したいときってありますよね。うんうん。

今まで

こんな感じでやってました

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

#define debug(x) cout<<#x<<" = "<<(x)<<endl
#define debug2(x,y) cout<<"("<<#x<<","<<#y<<") = ("<<(x)<<","<<(y)<<")"<<endl
#define debug3(x,y,z) cout<<"("<<#x<<","<<#y<<","<<#z<<") = ("<<(x)<<","<<(y)<<","<<(z)<<")"<<endl

int main(){
  int a = 10;
  double b = 3.14;
  string c = "hoge";

  debug(a);
  debug2(a,b);
  debug3(a,b,c);
}

出力はこんな感じ

a = 10
(a,b) = (10,3.14)
(a,b,c) = (10,3.14,hoge)

ここで使っているのが、「文字列化演算子#です。変数の名前自体を文字列に変換して出力できるので、どの変数がどの値なのかわかりやすいです。

可変引数テンプレート

ただ、変数の数ごとに使い分けをしているのがアホらしいですね。ここで、どこかで小耳に挟んだ「可変引数テンプレート」を使います。

パラメータパックを最初の要素から順番に処理していきたい場合には、「任意の型のパラメータをひとつと、任意の個数の任意の型のパラメータを受け取る」というような形式のパラメータリストとし、再帰によって処理をする:
可変引数テンプレート - cpprefjp C++日本語リファレンス

とのことなのでそこに書いてあるのをほぼコピペして書きます。

追記 : 改良しました。今までは改行の手前に余計なが入っていたけどなんか気持ち悪いので引数1つになったところで分岐(?)するようにしました(これ以降はそのままになっていますので各自書き換えをm( )m)

//旧
void debug(){cout<<endl;} //引数がなくなったときのためのdebug()

template <class H, class... Ts>
void debug(H&& h, Ts&&... ts){
  cout<<h<<" ";
  debug(forward<Ts>(ts)...);
}

//新
template <class H>
void debug(H&& h){
  cout<<h<<endl;;
}

template <class H, class... Ts>
void debug(H&& h, Ts&&... ts){
  cout<<h<<" ";
  debug(forward<Ts>(ts)...);
}
int main(){
  int a = 10;
  double b = 3.14;
  string c = "hoge";

  debug(a); //10
  debug(a,b); //10 3.14
  debug(a,b,c); //10 3.14 hoge
}

右辺値参照&&とかfoward()とかは、よくわかんなかったけど動くからええかって…………(一応参考: forward - cpprefjp C++日本語リファレンス, 右辺値参照・ムーブセマンティクス - cpprefjp C++日本語リファレンス

続きを読む

グラフの関節点と橋を求めて再帰DFSを知る

こんにちは。再帰のDFS(深さ優先探索)っていままでよくわからなくて、スタックを使えばBFS(幅優先探索)の書き換えで済むじゃんと思っていました。過去記事でもスタックDFSを使っています。

o-treetree.hatenablog.com

ただ、スタックDFSは「順番がDFSであること」くらいしか良いことがないのでこれを機に再帰DFSを使えるようになりたいですね。

題材はAOJの

です。これはスタックDFSでは(すんなりとは)解けないので再帰DFSを使う必要があります。

  • BFSとDFS2種の比較
    • BFS
    • スタックDFS
    • 再帰DFS
  • 関節点と橋
    • lowlink
      • 図解
    • 関節点と橋の検出
  • 実装
    • 行きがけ順(preorder)の記録
    • 帰りがけ順(postorder)の記録
    • lowlinkの記録
    • 橋の検出
    • 関節点の検出
    • 一応メイン関数とか
  • まとめ
  • グラフ情報
  • 参考

BFSとDFS2種の比較

BFS

スタックDFS

再帰DFS

  • 再帰で実装
  • 頂点 $u$ を探索するとき、$u$ の子についての情報を使える(←嬉しい!)

関節点と橋

以下のように関節点と橋が定義されています(詳しくは参考サイトへ)

  • 関節点 - 頂点 $u$ とそこにつながっている辺を取り除くと連結成分が増える ←→ $u$ は関節点
  • 橋 - 辺 $uv$ を取り除くと連結成分が増える ←→ 辺 $uv$ は橋

lowlink

これを求めるのにlowlinkという概念を使います。

  • まずグラフにDFSをして訪問順(行きがけ)の番号 $ord$ を振る
  • DFSで通らなかった辺を「後退辺」という
  • DFS木を根から葉へ進むか(何回でも)、後退辺を高々1回(1回以下)使って行ける頂点の $ord$ のうち最小のものを lowlink という

図解

このようなグラフを用意しました

f:id:o-treetree:20200607121936j:plain

頂点 $0$ からDFSをしたときの訪問順序 $ord$ を記録します。赤い辺が後退辺です

f:id:o-treetree:20200607121952j:plain

$lowlink$ は赤字のようになります

f:id:o-treetree:20200607122001j:plain

続きを読む

AIZU ONLINE JUDGE GRL_1_A 単一始点最短経路 を解くダイクストラ法の実装

こんにちは。

o-treetree.hatenablog.com

ちょっと前にAOJの問題を解いて動的計画法を学びましたが↑、グラフも勉強しようかなって思ってグラフのページを開きました。1番最初にあったのが

単一始点最短経路

です。何も知らない僕は最初幅優先探索でやりましたがWAになるのでwikipediaを調べたところ

単一始点最短経路問題 (SSSP:Single Source Shortest Path)
特定の1つのノードから他の全ノードとの間の最短経路問題。この問題を解くアルゴリズムとしては、ダイクストラ法やベルマン-フォード法がよく知られている。
最短経路問題 - Wikipedia

とのことだったので、前から噂には聞いていたけどやったことのないダイクストラに挑戦することにしました 。

  • 挑戦前のレベル
    DFSやBFSが一応はわかるくらいにグラフを扱ったことがある

今回主に参考にしたのは ダイクストラ法が分からなかった君のために - kuuso1のブログ です。

問題

単一始点最短経路から一部改変)

与えられたグラフと始点 $r$ について、単一始点最短経路の重みを求めるプログラムを作成してください。ノード $r$ を始点とし、$r$ から各ノードについて、最短経路上の辺の重みの総和を出力してください。

入出力

  • 入力

\begin{align} &V \quad E \quad r\cr &s_0 \quad t_0 \quad d_0\cr &s_1 \quad t_1 \quad d_1\cr &:\cr &s_{E-1} \quad t_{E-1} \quad d_{E-1} \end{align}

ここで $V,E$ はグラフの頂点と辺の数。 グラフの頂点にはそれぞれ $0,1,...,V−1$ の番号が付けられている。$r$は始点の番号である。$s_i, t_i$ はグラフの $i$ 番目の辺が結ぶ2つの頂点を表す(有向)。$d_i$ は $i$ 番目の辺の重みである。

  • 出力

$i$ 行目に $r→i$ の最短経路長を出力する

制約

  • $1 \le V \le 100000$
  • $0 \le d_i \le 10000$
  • $0 \le E \le 500000$
  • グラフに平行辺はない
  • グラフに自己ループはない

ダイクストラ法の実践

悩んだが説明できる気がしないので、「何をしたいか」をグラフを使って見せていきます。使うグラフはダイクストラ法が分からなかった君のために - kuuso1のブログで使われているグラフです。

f:id:o-treetree:20200604171758p:plain

続きを読む

第三回アルゴリズム実技検定に参加しました。

past.atcoder.jp

タイトル通りです。

無料でやらせてくれるっていうのでやってみました。

結果は64点で中級でした。わーい(⌒▽⌒)

受験終了の2020/6/6 18:00 JSTまで、これ以上のことを言及してはいけないので、これでおしまいです。

↓言及解禁

第三回 アルゴリズム実技検定 - H - おはやし日記

AtCoder ABC169 反省会 [A,B,D,E完]

こんにちは。ABC169でした。

atcoder.jp

感想

なんでぇ?

それでは、振り返っていきましょう。スピード重視の雑解説(?)です。

A

やるだけ。33秒で提出できました。

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

int main(){
  int A,B;cin>>A>>B;
  cout<<A*B<<endl;
}

B

一度は後回しにしたが無理やり通した。配列Aとは別に、log10(A)の配列Bを用意した。

  • とりあえず掛け算に0があれば0を出力して終わる
  • longlong と、log10とで同時に計算して、Bの和logansが18を越えていたらAの積ansも1018を越えるので-1を出力して終わる
  • ただ、たとえば ans = 1018 +1 のとき、誤差の問題でlogans=18となって通過してしまうので(そのくらいならlonglongでオーバーフローはしていないので)愚直に1018 との比較で落とす
  • 全て通過すればそのままansを出力
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using vi = vector<int>;
#define all(v) (v).begin(), (v).end()

const ll m = 1000000000000000000;

int main(){
  int N;cin>>N;
  ll ans = 1;
  vector<ll> A(N);
  vector<double> B(N);
  for(int i=0; i<N; i++){
    cin>>A[i];
    B[i] = log10(A[i]);
  }
  if(find(all(A), 0) != A.end()){
    cout<<0<<endl;
    return 0;
  }
  double logans = 0;
  for(int i=0; i<N; i++){
    ans *= A[i];
    logans += B[i];
  }
  if(logans > 18){
    cout<<-1<<endl;
    return 0;
  }
  if(ans > m){
    cout<<-1<<endl;
    return 0;
  }
  cout<<ans<<endl;
}

C

なぜWA?後回し

(23:23追記)

浮動小数点の誤差の問題だったそうです。

僕の解答WA

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

int main(){
  ll A;
  double B;
  cin>>A>>B;
  ll ANS = A * (ll)(B*100);
  cout<<ANS/100<<endl;
}

なんで通らないんだ?と思っていたのですが、B*100のところが競プロ的に「雑」だったということらしく、詳しくは↑の記事を見てもらえばいいのですが、まあそういうことで。

AC

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

int main(){
  ll A;
  double B;
  cin>>A>>B;
  ll ANS = A * (ll)((B+0.00001)*100);
  cout<<ANS/100<<endl;
}

今度からはこの手には引っかからんぞ!!!!!

(追記おしまい)

D

手をつけて15分しないで解けてる。

まず、Nを素因数分解します。例えば N = 1728 = 26 * 33 を考えると

2 2 2 2 2 2 3 3 3

とばらして、各素数ごとに1個、2個、と取り去っていく回数が答えです。

[2] [2 2] [2 2 2] [3] [3 3]

という感じで、5回に分けてとっていけるので N=1728について答えは5です。素因数分解する関数を既に持っていたのでそれを流用してサクッと解けました。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define ff first
#define ss second

  /*
  判定する数:n
  素因数分解の結果を格納する:l
  返り値:素因数の数
  */
long long prime_fac(long long n, vector<pair<long long, long long> >&l){
  long long s = floor(sqrt(n));
  long long r;
  pair<long long, long long> x;
  for(long long i=2; i<=s; i++){
    if(n%i==0){
      r=0;
      do{
        r++;
        n /= i;
      }while(n%i==0);
      x = make_pair(i, r);
      l.push_back(x);
    }
  }

  if(n>s){
    x = make_pair(n, 1);
    l.push_back(x);
  }

  return l.size();
}

ll counter(ll n){
  ll ans = 0;
  for(ll i=1; i<=n; i++){
    n -= i;
    ans++;
  }
  return ans;
}

int main(){
  ll N;cin>>N;
  vector<pair<long long, long long> >l;
  prime_fac(N, l);

  ll ans = 0;
  for(pair<long long, long long>x: l){
    ans += counter(x.ss);
  }
  cout<<ans<<endl;
}

vector<pair<long long, long long> >lには、(素因数, 指数) の組が入ります。

1728を素因数分解した時は

{ pair(2, 6), pair(3, 3) } のようになる(イメージ)。

E

これも手をつけてから15分しないで解けている。

AとBをそれぞれ数列に入れ、ソートすることで「下限の数列」と「上限の数列」がわかります。

それぞれ中央値を求めると「中央値の下限」と「中央値の上限」がでて、下限から上限まで

  • Nが奇数だったら1刻みで
  • Nが偶数だったら1/2刻みで

全ての値を取れる(気がした)のでそのまま実装したらACになりました。証明はしてない。

(23:40追記)公式解説pdfに、全部の値を取れるって書いてありました。良かった。https://img.atcoder.jp/abc169/editorial.pdf(追記終わり)

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

using vi = vector<int>;
#define all(v) (v).begin(), (v).end()

int main(){
  int N;cin>>N;
  vi A(N);
  vi B(N);
  for(int i=0; i<N; i++){
    cin>>A[i];
    cin>>B[i];
  }

  sort(all(A));
  sort(all(B));

  int L,R;
  if(N%2==1){
    L = A[N/2];
    R = B[N/2];
    cout<<( R-L+1 )<<endl;
  }else{
    L = A[N/2] + A[N/2-1];
    R = B[N/2] + B[N/2-1];
    cout<<( R-L+1 )<<endl;
  }
}

Cが解けたら追記します。おしまい。

AtCoder ABC134 E - Sequence Decomposing を解く [C++]

こんにちは。今夜はABC169です。ここ数日競プロに触ってなかったので、やっとくか!ということで未解答だったABC134に挑戦しました。そしたらE問題が、先日勉強したDPの応用ですらっと解けて嬉しかったので書いています。

o-treetree.hatenablog.com

こちらの記事で取り上げた考え方が使えました。

  • 問題
    • 制約
    • 入出力
    • 入出力例
  • 試行錯誤
  • DPテーブルの作成
  • 解答
  • おしまい
  • 参考

問題

atcoder.jp

↑から転載、一部改変

$N$ 個の整数からなる数列 $A=\{A_1,A_2,⋯,A_N\}$ が与えられます。$N$ 個それぞれの整数に対して、色を $1$ つ選んでその色を塗ります。 この時、以下の条件を満たす必要があります: $$ A_i と A_j(i<j) が同じ色で塗られているならば A_i<A_jが成立する $$ 条件を満たすように色を塗る時、用いる色の数の最小値を求めてください。

制約

  • $1 \le N \le 10^{5}$
  • $0 \le A_i \le 10^{9}$

入出力

//入力
N
A_1 A_2 ... A_N
//出力
Ans

(見やすさのため数列 $A$ を空白区切り1行で入力するとします(動作は(C++では)同じなので))

入出力例

(例1)

//入力
5
2 1 4 5 3
//出力
2
  • 2, 3 を赤色
  • 1, 4, 5 を青色

で塗ると条件を満たします

続きを読む
プライバシーポリシー ・お問い合わせはこちら