競プロ典型90問 084 - There are two types of characters

典型90問 084

問題文

o と x からなる長さ N の文字列 S が与えられます。

以下の条件をすべて満たす整数の組 (l,r) の個数を求めてください。

  • 1≤l≤r≤N
  • S の l 文字目から r 文字目までの区間に、o と x 両方が含まれる

制約

  • 1≤N≤106
  • S は o, x からなる長さ N の文字列である

考えたこと

二重ループで愚直に探すのは制約上不可能なので、
ランレングス圧縮で同一マークがどれだけ続いているか調べ、
調べあげた文字数と、それ以降の残数から導く方法をとった。
調べあげた文字数は、l に該当し圧縮処理が途切れた、つまり別のマーク(r に該当)に、
出会ったことになるため、l までの個数と r 以降の個数を掛け合わせれば良い
あとは、上記を S の最後まで繰り返し足し合わせていけば完了

コード

import fs from 'fs';

const main = (): void => {
  const input = fs.readFileSync(0, 'utf-8').trim().split('\n');
  const N = Number(input[0]), S = input[1];
  let total = 0;
  for (let i = 0; i < N;) {
    let j = i;
    // 同一文字数チェック(圧縮のメイン)
    while (j < N && S[i] === S[j]) j++; 
    // 同一文字数 x 全体から同一文字数分を引いた数
    i = j;
    total += (j - i) * (N - j);
  }
  console.log(total);
}
main();

ABC108 C

ABC108-C - Triangular Relationship

考えたこと

制約上、for文を1つに抑えたかったので愚直にa,b,cを全探索はできないため、色々考えた。
mod Kの結果として考えられる値は、K-1なのでその範囲でどうにか実装できないか、
mod Kの結果は1...K-1, 0の範囲をN/K週(プラスN mod K)分繰り返されることを、
理解できたからその結果を元にどうにか実装できないか、結局は以下の考え方でなんとかできた。

1...Nまでの mod Kを持つリストを用意し、
そのリスト内に条件を満たす要素がいくつ存在するかを探した。
条件(Kの偶奇によって分岐)
偶数
 mod K の値はN/2もしくは0であること
奇数
 mod K の値は0であること
上記の条件をスルーしたリスト内の要素を数え、その要素数の3乗の合計値が答えとなる。
3乗となる理由は、a,b,cの3パターンに格要素全てが考えられる、
例えば、(a,a,a), (a,a,b), (b,a,a), (a,a,b)これらはすべて別の扱いとなる。

import fs from 'fs';
const input = fs.readFileSync('/dev/stdin', 'utf-8').trim();

const main = (): void => {
  const [N, K] = input.split(/\s+/).map(Number);
  const list: number[] = [];
  for (let i = 1; i <= N; i++) {
    list.push(i%K);
  }
  if (K%2===0) {
    const tmp2 = list.filter(e => e === K/2);
    const tmp0 = list.filter(e => e === 0);
    console.log(tmp2.length**3 + tmp0.length**3);
  } else {
    const tmp0 = list.filter(e => e === 0);
    console.log(tmp0.length**3);
  }
}
main();

AtCoder Begginer Contest 084

ABC084-D.2017-like Number

概要

N も (N + 1) / 2 も素数」を満たす奇数 N
上記を満たす値を2017に似た数とし、
与えられた区間にいくつ存在するかを問われている。

考え

1..105(制約 r <= 105)の区間で2017に似た数の総数を保持する累積和を作って、
与えられた、l , r (r+1) の区間で似た数が存在するかを出力すればいけると考えた。

素数か否か

i が素数か否かを表す配列を用意

  const rMax = 10**5;
  // エラトステネスの篩
  const flag = (): number[] => {
    const list = Array.from({ length: rMax },() => 1);
    list[0] = 0, list[1] = 0;
    for (let i = 2; i <= rMax; i++) {
      if (list[i]) {
        for (let j = i*2; j <= rMax; j+=i) {
          list[j] = 0;
        }
      }
    }
    return list;
  }

2017に似た数か否か

素数判定の配列に対して、
問いの条件を満たすか否かを表す配列を用意

  const flagList = flag();
  const prefix = Array.from({ length: rMax }, () => 0);
  for (let i = 0; i < rMax; i++) {
    if (flagList[i] && flagList[Number((i+1)/2)]) {
      prefix[i] = 1; 
    }
  }

累積和

2017に似た数が i までに幾つ存在するかを表す配列

  const a = Array.from({ length: rMax+1 }, () => 0);
  for (let i = 0; i < rMax; i++) {
    a[i+1] = a[i] + prefix[i];
  }

提出コード

import fs from 'fs';
const input = fs.readFileSync('/dev/stdin', 'utf-8').trim();

const main = (): void => {
  const rMax = 10**5;
  // エラトステネスの篩
  const flag = (): number[] => {
    const list = Array.from({ length: rMax },() => 1);
    list[0] = 0, list[1] = 0;
    for (let i = 2; i <= rMax; i++) {
      if (list[i]) {
        for (let j = i*2; j <= rMax; j+=i) {
          list[j] = 0;
        }
      }
    }
    return list;
  }
  // 2017-likeかどうか
  const flagList = flag();
  const prefix = Array.from({ length: rMax }, () => 0);
  for (let i = 0; i < rMax; i++) {
    if (flagList[i] && flagList[Number((i+1)/2)]) {
      prefix[i] = 1; 
    }
  }
  // 累積和
  const a = Array.from({ length: rMax+1 }, () => 0);
  for (let i = 0; i < rMax; i++) {
    a[i+1] = a[i] + prefix[i];
  }
  const lines = input.split(/\s+/).map(Number);
  const Q = lines[0];
  for (let i = 0; i < Q; i++) {
    const right = lines[i*2+1];
    const left = lines[i*2+2];
    console.log(a[left+1]-a[right])
  }
}
main();

TypeScriptの型定義で個人的忘れがちなこと

概要

以下の警告によく出会う。

Parameter 'val' implicitly has an 'any' type, but a better type may be inferred from usage. [7044]

暗黙anyだけど、もっと良い型ありそう〜って言ってる。

ミス

TSに不慣れなもので当てずっぽうに型を明示するが、
一向に直らない。

const copy = input.split('').filter(val => !isNaN(Number(val))).join('');
↓
const copy = input.split('').filter(val: string => !isNaN(Number(val))).join('');

正解

型を明示する際は、アロー関数の変数を()括弧で囲む必要がある。

const copy = input.split('').filter((val: string) => !isNaN(Number(val))).join('');

これ、よく忘れる。。。

nvim-cmpを導入(init.lua)

What

入力補完をオートで表示してくれるプラグイン
ビルトインではオムニ補完をキー操作で行えるが、
そろそろLSPのセッティングもちゃんとしようということで入れた

環境

エディター - Neovim : 0.11.6

プラグインマネージャー - vim-plug

手順

nvim-cmpの推奨する設定通りに進めていく

1. プラグイン

nvim-lspconfig
    LSPの設定を自分で書かずとも最低限の設定をしてくれるもの
cmp-nvim-lsp
    LSPとnvim-cmpの橋渡し
cmp-buffer
    カレントバッファ内の単語を補完ソースとする
cmp-path
    ファイルパスの補完
cmp-cmdline
    nvim上でのコマンドの補完
nvim-cmp
    補完フレームワーク本体
cmp-vsnip
    nvim-cmpとvim-vsnipの橋渡し
vim-vsnip
    スニペットエンジン

-- nvim/init.lua
vim.cmd([[
  call plug#begin()

  Plug 'neovim/nvim-lspconfig'
  Plug 'hrsh7th/cmp-nvim-lsp'
  Plug 'hrsh7th/cmp-buffer'
  Plug 'hrsh7th/cmp-path'
  Plug 'hrsh7th/cmp-cmdline'
  Plug 'hrsh7th/nvim-cmp'

  " For vsnip users.
  Plug 'hrsh7th/cmp-vsnip'
  Plug 'hrsh7th/vim-vsnip'

  call plug#end()
]])

require('lsp.init')
require('plugins.cmp')

2. 設定

LSPの設定

LSPから入力候補を取得できるようセッティング
dotfilesで管理しており別端末にクローンした初動でエラーとならないよう、 pcall()でビルトインの補完と拡張された補完データの取得を分けている

-- nvim/lua/lsp/init.lua
local lsp_names = {
  'eslint',
  'lua_ls',
    'ts_ls'
}

local ok, cmp_nvim_lsp = pcall(require, 'cmp_nvim_lsp')
local capabilities = ok and cmp_nvim_lsp.default_capabilities() or vim.lsp.protocol.make_client_capabilities()

for _, name in ipairs(lsp_names) do
  vim.lsp.config(name, {
    capabilities = capabilities,
  })
end

vim.lsp.enable(lsp_names)

-- pcall()なし
local capabilites = require('cmp_nvim_lsp').default_capabilities()
-- 省略

nvim-cmpの設定

プラグイン経由で入力補完を使用可能にする基本的な設定や、
キーマッピングの設定

-- nvim/lua/plugins/cmp.lua
local ok, cmp = pcall(require, 'cmp')
if not ok then return end

cmp.setup({
  snippet = {
    expand = function(args)
      vim.fn["vsnip#anonymous"](args.body)
    end,
  },
  mapping = cmp.mapping.preset.insert({
    ['<C-n>'] = cmp.mapping.select_next_item(),
    ['<C-p>'] = cmp.mapping.select_prev_item(),
    ['<C-b>'] = cmp.mapping.scroll_docs(-4),
    ['<C-f>'] = cmp.mapping.scroll_docs(4),
    ['<C-Space>'] = cmp.mapping.complete(),
    ['<C-e>'] = cmp.mapping.abort(),
    ['<CR>'] = cmp.mapping.confirm({ select = true }),
  }),
  sources = cmp.config.sources({
    { name = 'nvim_lsp' },
    { name = 'vsnip' },
  }, {
    { name = 'buffer' },
    { name = 'path' },
  }),
})

-- `/` 検索補完
cmp.setup.cmdline('/', {
  mapping = cmp.mapping.preset.cmdline(),
  sources = {
    { name = 'buffer' },
  },
})

-- `:` コマンドライン補完
cmp.setup.cmdline(':', {
  mapping = cmp.mapping.preset.cmdline(),
  sources = cmp.config.sources({
    { name = 'path' },
  }, {
    { name = 'cmdline' },
  }),
})

3. インストール

色々設定が終わったら、 :PlugInstall 実行でNeovim再起動して使えるはずです。


終わり

ブランチを切りそこねた話

経緯

Vanilla TSの学習とGitHubのissueの使用方法などを学習がてら、
ブランチを切ってコードを書いていたら卿が乗り、
属していたブランチとは関係のない作業まで行ってしまった。
コミットする時に気づき、
ブランチを後からどうにかできないかと思った。

手順

  1. 現在属しているブランチに関係するもののみをコミット
  2. 新しいブランチを作成して、コミット
git checkout -b new-branch
git add .
git commit -m 'add: XXX'

整理が終わったらリモートへプッシュで完了

結論

やらかしたと思った時は大抵焦る。
でも、大抵そういうものは想定された作りになっているから、
焦らず冷静に調べればどうにでもなると思った。

neovimへESLint + Prettierの導入(最低限)

環境

エディター : Neovim
パッケージマネージャー : なし
プラグインマネージャー : vim-plug

概要

ESLint

設定ファイルに記したコーディングスタイルのルールで、 ファイル全体を静的に解析し、バグの温床となりやすいルールに沿わないコードを検出してくれる

Prettier

コードの自動整形ツール

ESLintとPrettierの違い

ESLint
 コードの品質向上のための一貫性
Prettier
 コードの可読性向上のための一貫性

例えば、未使用の変数などに指摘が入るのはESLint

設定手順

ESLint

事前にvscode-langservers-extractedをグローバルにインストール

npm install -g vscode-langservers-extracted

詳細は以下を参照
vacode-langservers-extracted

ESLintのLSPを有効化
lsp/init.lua

vim.lsp.enable('eslint');



Prettier

nvim/init.luaプラグイン追加

vim.cmd([[
  call plug#begin()
// 省略
  Plug 'prettier/vim-prettier', {
     \ 'do': 'npm install --frozen-lockfile --production',
     \ 'for': ['javascript', 'typescript', 'css', 'less', 'scss', 'json', 'graphql', 'markdown', 'vue', 'svelte', 'yaml', 'html'] }
  call plug#end()
]])

vim.cmd()でラップしvimscriptの記述を可能にしてる
vim-plugのInstallingが基本vimscriptだから)


Prettier本体のinstall

npm install -g prettier

面倒だったので自分はグローバルにインストールをしたが、
プロジェクト単位でPrettierのバージョンを変えたい人は要注意

最後に

実際の設定は以下を参照ください
Github dotfiles