TotalTech

フリーランスのプログラマーが、技術情報・ガジェット・仕事術について書いてゆきます。資料価値の高い記事を目指しています。コーヒー好きです。

TotalTech

AtCoder ABC105: D - Candy Distribution

区間和は「累積和の差分」で考えてみましょう。水色diff です。

問題

数列 A_1, \cdots, A_{N}区間 (l, r)A_l + A_{l+1} + \cdots + A_rM の倍数であるものの数を求めよ。

制約

  •  1 \le N \le 10^5
  •  1 \le M \le 10^5
  •  1 \le A_i \le 10^9

考察

\{A_i\} の累積和 \{S_i\} を計算し、


\begin{aligned}
A_l + A_{l+1} + \cdots + A_r = \text{(}M\text{の倍数)} &\Longleftrightarrow S_{r} - S_{l-1} = \text{(}M\text{の倍数)} \\
&\Longleftrightarrow S_{r} \equiv S_{l-1} \quad \text{(mod} M \text{)}
\end{aligned}

とします。

求める値は、\{S_i\} の剰余の数列 \{T_i\} で、同じ値が何度出てくるかの総和と等しくなります。

Pythonでは collections.Counter が便利です。 values() で出現回数が取得できます。

実装例

N, M = map(int, input().split())
A = list(map(int, input().split()))

S = [0] + list(itertools.accumulate(A))
T = [s % M for s in S]
counter = collections.Counter(T)
ans = sum(n * (n-1) // 2 for n in counter.values())
print(ans)

AtCoder 第六回 アルゴリズム実技検定 (PAST): N - 活動

「条件を手で計算してみる大切さ」を教えてくれる問題です。 ソートしてからナップサックDPで解けます。

問題

N 種類の活動の中から 1 つ以上を選び、好きな順序で行なう。 最初の体力は H であり、活動 i を行うと、得点 a_i \times \text{(体力)} を得た後、体力が b_i 減少する。 得られる得点の和の最大値を求めよ。

制約

  •  1 \le N \le 100
  •  1 \le H \le 10^5
  •  1 \le a_i, b_i \le 10^5

考察

活動を行った時に得られる得点が「その時点の体力」に依存するので、そのままナップサックDPは使えません。 しかし、活動の順序を全検索(100! 通り)することは、時間的に不可能です。

ここで、活動 1 → 活動 2 を行った場合と、活動 1 → 活動 2 を行った場合の、それぞれ得られる得点を計算してみます。 体力の減少は、どちらも同じ b_1+b_2 です。


\displaystyle
\begin{aligned}
& \left\{
    \begin{array}{l}
        体力 \, h \\
        得点 \, 0
    \end{array}
\right.
& \xrightarrow{活動1} \,
& \left\{
    \begin{array}{l}
        体力 \, h-b_1 \\
        得点 \, a_1 h
    \end{array}
\right. 
& \xrightarrow{活動2} \,
& \left\{
    \begin{array}{l}
        体力 \, h-b_1-b_2 \\
        得点 \, a_1 h + a_2(h-b_1)
    \end{array} \\
\right. \\
& \left\{
    \begin{array}{l}
        体力 \, h \\
        得点 \, 0
    \end{array}
\right. 
& \xrightarrow{活動2} \,
& \left\{
    \begin{array}{l}
        体力 \, h-b_2 \\
        得点 \, a_2 h
    \end{array}
\right. 
& \xrightarrow{活動1} \,
& \left\{
    \begin{array}{l}
        体力 \, h-b_2-b_1 \\
        得点 \, a_2 h + a_1(h-b_2)
    \end{array} \\
\right. 
\end{aligned}

得られる得点の差は、


\displaystyle
\begin{aligned}
\text{score}_{\text{活動1} \rightarrow \text{活動2}} - \text{score}_{\text{活動2} \rightarrow \text{活動1}} 
&= \left\{a_1 h + a_2(h-b_1) \right\} - \left\{a_2 h + a_1(h-b_2) \right\} \\
&= a_1 b_2 - a_2 b_1
\end{aligned}

それで、


\displaystyle
\begin{aligned}
\text{score}_{\text{活動1} \rightarrow \text{活動2}} \ge \text{score}_{\text{活動2} \rightarrow \text{活動1}}
& \Longleftrightarrow a_1 b_2 - a_2 b_1 \ge 0 \\
& \Longleftrightarrow \frac{a_1}{b_1} \ge \frac{a_2}{b_2} \\
\end{aligned}

となります。ゆえに、 \displaystyle \frac{a_i}{b_i} が大きい方から処理する(降順でソート)のが最善です。

実装例

INF = 10**10
 
N, H = map(int, input().split())
 
Activities = []
for i in range(N):
    a, b = map(int, input().split())
    Activities.append((a, b))
Activities.sort(key=lambda x: (x[0]/x[1]), reverse=True)
 
dp = [[-INF] * (H+1) for _ in range(N+1)]
dp[0][H] = 0

for i in range(N):
    A, B = Activities[i]
    for h in range(H+1):
        dp[i+1][h] = max(dp[i+1][h], dp[i][h]) 
        if h > 0:
            h1 = max(h-B, 0)
            dp[i+1][h1] = max(dp[i+1][h1], dp[i][h] + A*h) 

print(max(dp[N]))

DP高速化

ここからは趣味の範囲です。

このコードで、PyPy で 336 ms でACできますが、さらに高速化できます。

\text{dp}_{i+1}\text{dp}_i にしか依存しないので、\text{dp}_0,\cdots,\text{dp}_N を管理するのではなく、\text{dp}_idpとする) と \text{dp}_{i+1}dp1とする)の2つのみ管理するようにします。 ループごとに dp1dp で更新し、ループの最後に dpdp1 に置き換えます。

dp = [-INF] * (H+1)
dp[H] = 0

for A, B in Activities:
    dp1 = [-INF] * (H+1)
    for h in range(H+1):
        dp1[h] = max(dp1[h], dp[h]) 
        if h > 0:
            h1 = max(h-B, 0)
            dp1[h1] = max(dp1[h1], dp[h] + A*h) 
    dp = dp1

print(max(dp))

これで、PyPy で 260 ms です。

さらに、配るDPの更新の時に大きい h のみを更新しているので、\text{dp} 1本を h=0,1,\cdots,H で更新していけばOKです。

dp = [-INF] * (H+1)
dp[H] = 0

for A, B in Activities:
    for h in range(H+1):
        if h > 0:
            h1 = max(h-B, 0)
            dp[h1] = max(dp[h1], dp[h] + A*h) 

print(max(dp))

これで、PyPy で 150 ms です。

ただし、これでもPythonではTLEします。 計算量 O(HN)10^7 なので、PyPyでないと時間が厳しいです。

AtCoder ABC208 D - Shortest Path Queries 2

ワーシャル・フロイド法の理解を問う問題です。 緑diffです。

問題

GN 頂点 M 辺の有向グラフ、 f(s,t,k)s から t へ頂点番号 k 以下の頂点のみ経由したときの最小距離とする。 \displaystyle \sum_{s=1}^N \sum_{t=1}^N \sum_{k=1}^N f(s,t,k) を求めよ。

制約

  •  1 \le N \le 400
  •  M \le N(N-1)

考察

全点間の距離なので、計算量 O(N ^3) のワーシャル・フロイド法が思いつきます。 しかし、k=1,\cdots,N に対してワーシャル・フロイド法を用いると、全体で O(N ^4) になり、間に合いません。

ここで、ワーシャル・フロイド法のコード

for k in range(N):
    for i in range(N):
        for j in range(N):
            if cost[i][k] != INF and cost[k][j] != INF:
                cost[i][j] = min(cost[i][j], cost[i][k] + cost[k][j])

を眺めると、一番外側の k のループがまさに、頂点 k を付加して全点間の距離を更新する処理になっていることが分かります。

それで \text{cost}_{i, j} を、

  • \text{cost}_{u, u} = 0  (u=1,\cdots,N)
  • \text{cost}_{A_i, B_i} = C_i
  • \text{cost}_{\text{other}, \text{other}} = \infty

で初期化し、k の順に処理します。 k の時の全点間の距離の和は差分処理で求めています。

実装例

INF = 10**15

N, M = map(int, input().split())
cost = [[INF] * N for _ in range(N)]
for i in range(N):
    cost[i][i] = 0

for _ in range(M):
    a, b, c = map(int, input().split())
    cost[a-1][b-1] = c

v = 0
for a in range(N):
    for b in range(N):
        v += (cost[a][b] if cost[a][b] < INF else 0)

ans = 0
for k in range(N):
    for a in range(N):
        for b in range(N):
            d = cost[a][k] + cost[k][b]
            if d < cost[a][b]:
                v += -(cost[a][b] if cost[a][b] < INF else 0) + d
                cost[a][b] = d

    ans += v

print(ans)

VB.NETで複数の値の「どれかに一致」を判定する方法

VB.NETで「どれかに一致」を判定する方法

よく、複数の値の「どれかに一致」を判定したいことがあります。

例えば、Excelのファイルを開くプログラムを作っているとします。Excelファイルの拡張子は、

  • .xls
  • .xlsx
  • .xlsm
  • .xlsb

があります。ユーザーが指定したファイルの拡張子が、このどれかに一致するかどうかを判定する必要があります。

そんな時の便利な書き方です。

続きを読む

商用利用可!海外のフリー写真素材サイトまとめ ― 2020年版

海外写真フリー素材サイトまとめ

海外のフリー写真素材サイトをまとめました。2020年の決定版です。

このページで紹介されているサイトは全て、ライセンスが

  • 商用利用も OK
  • 編集・合成等 OK
  • 使用時に出典の明記の義務なし
    ("photo by ~"、みたいな文言や、サイトへのリンク。英語では"attribute"という)

の条件を満たすものだけ選びました。多くのサイトが、美麗な写真を クリエイティブ・コモンズ・ゼロ のライセンスで公開しています。

加えて、

  • キーワードで写真を検索できる

という条件もつけました。

無料でプロレベルのクオリティの写真がたくさんありますよ!

続きを読む

【Excel】不要なチェックボックスを消す方法

f:id:totaltech365:20200315145017p:plain

Excelでウェブサイトからコピーして貼り付けたりした時に、チェックボックスが残ることがあります。

今回は、不要なチェックボックスを消す(削除する)方法をご紹介します。

続きを読む

将棋用語を辞書登録!便利な無料の「IME用将棋用語辞書」

将棋用語を辞書登録!便利な無料の「IME用将棋用語辞書」

「IME用将棋用語辞書」

将棋ブログや感想戦などで将棋用語を入力する際、一発できちんと変換されず、煩わしく思うことはありませんか。

今回は、将棊用語を一発で辞書登録できる無料の辞書データIME用将棋用語辞書」をご紹介します。

これを使えば、例えば「せんて76ふ」と入力すると、「▲7六歩」と変換されるようになります。

私もインストールしていますが、ブログ執筆や将棊の勉強に、とても便利です。

続きを読む

実戦棋譜001 四間飛車 対 居飛車持久戦

四間飛車 対 居飛車持久戦

棋力向上のためには、実戦で人と指すだけでなく、実戦を振り返って反省することが欠かせません。

対戦相手と感想戦を行なって、間違った手を指摘してもらったり、変化を一緒に考えたりするのが理想です。しかしネット将棋で感想戦は難しいです。将棋ウォーズでは感想戦を行なうシステム自体ありません。

それで、自分の気力向上のために、実戦棋譜と自分なりの分析をブログに載せて、見ていただこうと思います。

現在の私:

  • 現在の実力: 将棋ウォーズ2級
  • 棋風・方針: 四間飛車だけ
  • 目標: 将棋ウォーズ初段

アマ2級の棋譜ですが、初心者と笑わずに、どうぞ見ていただければと思います。そして感想やアドバイスを、コメントやTwitterで寄せていただければ、大変嬉しいです。

将棋ウォーズ10分切れ負けの棋譜です。ソフトは elmo + やねうら王 を使っています。

続きを読む

VB.NETでSingletonパターンを用いる方法

VB.NETでSingletonパターンを用いる方法

デザインパターンの一つに、Singleton(シングルトン)というものがあります。プログラム全体で、そのクラスのインスタンス(実体)が1つである事を保証する、デザインパターンです。

アプリケーション全体を表すクラスなど、複数のインスタンスが必要ない(許容したくない) 時に使います。

今回は、VB.NETでSingletonパターンを使う方法を、解説します。

続きを読む

職場や子どものデスクに地球儀を置くメリットと、オススメ地球儀

デスクに地球儀

成功した企業の創業者や、有名な大学教授の机には、よく地球儀が置いてありますよね。(勝手なイメージ)

デスクにインテリアとして地球儀が置いてあると、それだけでアカデミックな雰囲気が出ます。

全てが手作りだった時代、地球儀は非常に高価なものでしたが、現在はAmazonなどで安価で小型な地球儀が手に入ります。

今回は、オフィスのデスクに、そして子どもたちの学習机に地球儀を置くメリットをご紹介します。

続きを読む