区間被覆問題

区間被覆問題(Interval Covering Problem)を効率的に解くためのPythonライブラリ(モジュール)

この問題は、「指定されたターゲット区間 $[S, E]$ を、与えられた複数の部分区間から最小限の個数を選んで完全にカバーする」という最適化問題です。効率的な貪欲法(Greedy Algorithm)を用いて、計算量 $O(N \log N)$ で最適解を求めます。

def solve_interval_cover(target_start, target_end, intervals):
    """
    ターゲット区間 [target_start, target_end] を最小の区間数で被覆する関数。

    Parameters:
        target_start (int/float): カバーしたい範囲の開始点
        target_end (int/float): カバーしたい範囲の終了点
        intervals (list of list/tuple): [開始, 終了] または [開始, 終了, ラベル] のリスト
                                         例: [[0, 3, "A"], [1, 5, "B"], ...]

    Returns:
        list: 選択された区間のリスト(各要素は入力と同じ形式)
    """
    # データを内部で扱いやすいように整形 (start, end, label)
    # ラベルがない場合は None を設定
    formatted_intervals = []
    for item in intervals:
        start, end = item[0], item[1]
        label = item[2] if len(item) > 2 else None
        formatted_intervals.append((start, end, label))

    # 開始点の昇順、同じ開始点なら終了点の降順でソート
    sorted_intervals = sorted(formatted_intervals, key=lambda x: (x[0], -x[1]))

    selected = []
    current_end = target_start
    n = len(sorted_intervals)
    i = 0

    while current_end < target_end:
        best_interval = None
        max_end = current_end

        # 現在のカバー範囲の開始時点(current_end)以下で始まり、
        # 最も遠くまで届く(endが最大の)区間を探す
        while i < n and sorted_intervals[i][0] <= current_end:
            if sorted_intervals[i][1] > max_end:
                max_end = sorted_intervals[i][1]
                best_interval = sorted_intervals[i]
            i += 1

        # 新たに範囲を広げられない場合はカバー不可能
        if best_interval is None or max_end <= current_end:
            return []

        selected.append(best_interval)
        current_end = max_end

    # 出力形式を入力のリスト構造(3要素なら3要素、2要素なら2要素)に合わせる
    output = []
    for item in selected:
        start, end, label = item
        if label is not None:
            output.append([start, end, label])
        else:
            output.append([start, end])

    return output
# 1. ターゲット範囲の設定
target_start = 0
target_end = 10

# 2. [開始, 終了, ラベル(任意)] のリスト形式で区間を定義
available_intervals = [
    [0, 3, "A"],
    [1, 5, "B"],
    [3, 8, "C"],
    [4, 9, "D"],
    [8, 10, "E"],
    [2, 4, "F"]
]

# ラベル無しのシンプル形式 [[0, 3], [1, 5], ...] でも動作します

# 3. 求解
result = solve_interval_cover(target_start, target_end, available_intervals)

# 4. 結果の出力
if result:
    print(f"最小被覆に必要な区間数: {len(result)}")
    print("選択された区間:")
    for iv in result:
        print(f"  - {iv}")
else:
    print("指定された範囲を完全にカバーすることは不可能です。")

ランダムケースを生成するプログラム

import random
import secrets

max = 2*10**5
K = 20

N = random.randint(2, K)
M = random.randint(0, K)
H = random.randint(1, K)
W = random.randint(1, K)

#==================================================
# list
NN = N
Lmin = 0
Lmax = N

A = []
B = []
for i in range(NN):
    a = random.randint(Lmin, Lmax)
    b = random.randint(Lmin, Lmax)
    A.append(a)
    B.append(b)

#==================================================

for i in range(M):
    u = random.randint(1, N)
    v = random.randint(1, N)
    # print(u, v)

#==================================================

print(N, M)
print(A)


#==================================================

import secrets
import string

# 使用する文字(英大文字・小文字・数字・記号)
alphabet = string.ascii_letters + string.digits + string.punctuation

# 16文字の安全なランダム文字列を生成
password = "".join(secrets.choice(alphabet) for i in range(16))

# print(password)  # 出力例: k#9$mP2!xL7%vR4#
# コピー元とコピー先のパスを指定
src_file = "/Users/Kagawa/bin/VS_runner/generate.py"
dst_file = "generate.py"

try:
    # ファイルをコピー
    shutil.copy(src_file, dst_file)
    print("generate.py のコピーが完了しました!")
except FileNotFoundError:
    print("コピー元のファイルが見つかりません。")
except Exception as e:
    print(f"エラーが発生しました: {e}")

セグメント木

区間最大値

区間最大値(Range Maximum Query)を効率的に処理できるセグメント木を簡単に作成・実装できます!

競技プログラミングなどでよく使われる、非再帰(配列ベース)の高速な実装をサンプルコードとしてご紹介します。この実装では、値の更新(Point Update)と、指定した区間の最大値取得(Range Max Query)を O(log N) で行えます。

Read more

巨大数における二項係数の計算

N および M が非常に大きく、N, M <= 10^6 程度の範囲であれば、階乗とその逆元(フェルマーの小定理を利用)を前計算または計算することで O(N+M) で求めるのが一般的です。

一方で、N, M がさらに大きく 10^9 規模になる場合は、単なる階乗計算では間に合わないため、Lucasの定理など別の手法が必要になります。

以下に N, M <= 10^6 程度の時に最も標準的かつ高速に動作する実装を示します。

MOD = 10**9 + 7
def combination_mod(n: int, m: int, mod: int = MOD) -> int:
    """
    (N + M)! / (N! * M!) % mod を計算する関数
    計算量: O(N + M)
    """
    total = n + m
    
    # 分子の (N + M)! を計算
    num = 1
    for i in range(1, total + 1):
        num = (num * i) % mod
        
    # 分母の N! * M! を計算
    den_n = 1
    for i in range(1, n + 1):
        den_n = (den_n * i) % mod
        
    den_m = 1
    for i in range(1, m + 1):
        den_m = (den_m * i) % mod
        
    den = (den_n * den_m) % mod
    
    # フェルマーの小定理を用いて分母の逆元を計算し、乗算する
    # den^(mod - 2) % mod
    return (num * pow(den, mod - 2, mod)) % mod
# 使用例
print(combination_mod(100000, 100000))

計算のポイント

  1. フェルマーの小定理による逆元計算
    1. MOD = 10^9 + 7 は素数であるため、割り算 A/B mod{MOD} は、A x B^{MOD-2} %{MOD} として計算できます。Pythonの pow(den, mod – 2, mod) は内部で繰り返し二乗法を使うため高速です。
  2. より高速化したい場合(複数回呼び出す場合)もしこの計算を複数回行う場合は、あらかじめ 1 から N+M までの階乗とその逆元を配列に前計算しておくことで、1回のクエリあたり O(1) で処理できるようになります。

Kadaneのアルゴリズム

Kadaneのアルゴリズムは、「配列の中の連続する部分列(部分配列)の和の最大値」を O(N) で求める方法です。

参考HP

def kadane(B):
    cur = B[0]
    best = B[0]
    for b in B[1:]:
        cur = max(b, cur + b)   # 続ける vs 新規スタート
        best = max(best, cur)   # 答えを更新
    return best

B = [2, -2, 3, 0, 1]  # 例:入力例1の A=[3,-1,4,1,2] を -1 したもの
print(kadane(B))  # 4

AWC0160 B の問題は、これを使えば簡単に解ける。

Read more

Binary Indexed Tree / フェニック木

LINK Binary Indexed Tree (BIT) アルゴリズムで動的に累積和を処理する 

BIT のライブラリ

class BIT:
    def __init__(self, n):
        self.size = n
        self.tree = [0] * (n + 1)

    def add(self, i, x):
        """index i (1-indexed) に x を加算"""
        while i <= self.size:
            self.tree[i] += x
            i += i & -i

    def sum(self, i):
        """1 から index i (1-indexed) までの区間和を取得"""
        s = 0
        while i > 0:
            s += self.tree[i]
            i -= i & -i
        return s

    def query(self, l, r):
        """区間 [l, r] (1-indexed) の和を取得"""
        return self.sum(r) - self.sum(l - 1)
Read more

heapq

AWC 0008D 果樹園の収穫

heapq をつかった貪欲法。python のheapq は、もっとも小さい値しか出力ができないので、最も大きい値を出力したいときは、負の値に変えて格納していく。
 heapq にリストを格納することは可能です。heapq は要素同士を <(小なり)で比較して優先度(最小値)を決定するため、リストを要素として入れた場合は 「リストの先頭要素から順番に比較」 されます。

Read more

重み付き区間スケジューリング問題

AtCoder Weekday Contest 0133 Beta/ D

まず、申請を終了時刻 Ri​ の昇順に並べ、その順に index をふり直します。

  • dp[i]=「1…i だけを考えたときの利益の最大値」

として貰う DP を考えると、申請 i について

  • 申請 i を承認しない場合dp[i−1] が最大値となります。
  • 申請 i を承認する場合これまでに承認した申請すべてが Rj​<Li​ を満たしてなければいけません。R の昇順に並べましたから、これを満たすような申請の個数を j として、 dp[j]+Vi​ が最大値となります。

よって、 dp[i]=max(dp[i−1],dp[j]+Vi​) と更新できます。

j は二分探索で求められることを考えると、ソートと二分探索を合わせて計算量は O(NlogN) です。

解説 

C++ プログラムを web site で実行する

Mac OS 上でコンパイルしたファイルは Linux 上では起動しない。Coreserver(Linux (64bit)) / Zenlogic (RHEL 8互換)

まずは、C++ でプログラムを書き、以下のようにコンパイルする。

      gcc program.c -static -o program.exec

      file を upload する。そのときにファイルパーミッションを実行モードにしておく

      php では、いろいろなことが考えられる。

      exec('program.exec', $output, $return_var);
      # 標準出力として $output に出る。ただし 配列で受け取る
      # エラー出力は $return_var に出る。
      
      system('program.exec');
      $output = shell_exec(ORG_ROOT . '/gcc002.exec');

      実行ファイルのディレクトを絶対的に記入しないと、起動しない。Zen logic / coreserver ではこんな感じ。

      #Zen logic 
      define('ORG_ROOT', "/virtual/htdocs/ibafami/www/u/");
      
      #coreserver
      define('ORG_ROOT', "/virtual/acphot2/public_html/sky/lemon/");
      

      1 2 3 … 9