C++で与えられた点を含むことができる線分の最大数を求めるアルゴリズム
この記事では、「与えられた複数の点を含むことができる線分の数」の最大値を求める問題について解説します。
問題概要
サイズ n1 の配列 a1[] と、2つの整数 A・B が与えられます。配列 a1[] の各要素からは、始点を a1[i] − A、終点を a1[i] + B とする n1 個の線分を作成できます。
さらに、n2 個の点からなる別の配列 a2[] が与えられます。これらの点を各線分に割り当てるとき、点が割り当てられた線分の数を最大化することが目標です。なお、1つの点を同じ線分に複数回割り当てることはできません。
具体例で理解しよう
入力例 1
a1[] = {1, 4, 5}, a2[] = {2, 8}, A = 1, B = 2出力例 1
1
説明: 点 a1[i] − A と a1[i] + B から作成できる線分は (0, 6) と (3, 7) の2本です。
配列 a2[] の最初の点「2」は最初の線分 (0, 6) に割り当てられますが、次の点「8」はどの線分にも含まれません。したがって、点を割り当てられる線分は1本だけで、出力は 1 となります。
入力例 2
a1[] = {1, 2, 3, 4, 6, 7}, a2[] = {2, 5, 6, 8}, A = 0, B = 1出力例 2
4
アルゴリズム(貪欲法)
この問題は、両方の配列をあらかじめソートしておき、貪欲法(Greedy法)で効率よく解くことができます。手順は以下の通りです。
- main 関数内で、ベクトル a1・a2 および整数 A・B を所定の値で初期化します。
- 変数 n1・n2 を作成し、それぞれベクトル a1・a2 のサイズを格納します。
- Max() 関数内で、まず両方のベクトル a1・a2 をソートします。
- ベクトル a2 の位置を追跡するために j = 0、最終的な答えを保持するために ans = 0 を初期化します。
- i = 0 から i < n1 まで for ループを回します。
- for ループの中で、条件 j < n2 を持つ while ループを開始します。
- (a1[i] + B < a2[j]) の場合、現在の線分の終点が対象の点より手前にあるため、while ループを抜けます。
- それ以外の場合、(a2[j] >= a1[i] − A && a2[j] <= a1[i] + B) かどうかを判定します。真であれば点をこの線分に割り当てられるので、ans と j をインクリメントし、while ループを抜けます。
- どちらの条件も満たさない場合は、j をインクリメントして次の点へ進みます。
- すべての処理が完了したら ans を返します。
C++実装例
#include <bits/stdc++.h>
using namespace std;
int Max(vector<int> a1, vector<int> a2, int n1, int n2, int A, int B){
// a1 と a2 をソート
sort(a1.begin(), a1.end());
sort(a2.begin(), a2.end());
int j = 0;
int ans = 0;
for (int i = 0; i < n1; i++){
// 点を探索
while (j < n2){
/* 線分の終点が現在の点より小さい場合 */
if (a1[i] + B < a2[j])
break;
// 点が線分の範囲内にある場合
if (a2[j] >= a1[i] - A && a2[j] <= a1[i] + B){
ans++;
j++;
break;
}
else
j++;
}
}
return ans;
}
// main 関数
int main(){
int A = 0, B = 1;
vector<int> a1 = { 1, 2, 3, 4, 6, 7 };
int n1 = a1.size();
vector<int> a2 = { 2, 5, 6, 8 };
int n2 = a2.size();
cout << Max(a1, a2, n1, n2, A, B);
return 0;
}出力
4
計算量の評価
両方の配列のソートに O(n1 log n1 + n2 log n2) の計算量が必要です。その後の二重ループでは、内側の while ループのポインタ j が後が後戻りしないため、各点は高々1度しか処理されず、O(n1 + n2) で完了します。全体の時間計算量は O(n1 log n1 + n2 log n2)、空間計算量は O(1)(入力を除く)となります。
-
【C++】グラフの連結性を保ちながら辺を削除し、スコアの最大削減量を求める方法
問題概要 n 個の頂点と m 本の辺からなる重み付き無向グラフを考えます。グラフの「スコア」は、含まれるすべての辺の重みの総和として定義されます。辺の重みは負になることもあり、そのような辺を取り除くとかえってスコアが増えてしまいます。 ここで求めたいのは、グラフを連結状態に保ったまま不要な辺を削除してスコアを最小化し、「スコアを最大でどれだけ減らせるか」を計算することです。 グラフは配列 edges として与えられ、各要素は {weight, {vertex1, vertex2}}(重みと両端の頂点)という形式で表されます。 入力例と出力 たとえば n = 5、m = 6、edges = {
-
C++でN個のセグメントを使って7セグメントディスプレイに表示できる最大の数を求める方法
問題の概要 この記事では、7セグメントディスプレイに対してN個のセグメントを使用したときに、表示できる最大の数を求める方法を解説します。 まず、具体例を使って何をすべきかを確認しましょう。 入力 − N=5 出力 − 71 説明 − この場合、最大の数は7セグメントディスプレイ上で次のように表示されます。 入力 − N=6 出力 − 111 アルゴリズムのアプローチ この問題は、次の3つの場合に分けて考えることができます。 ケース1 −Nが0または1の場合、どの数字も表示できません。 ケース2 −Nが奇数の場合です。奇数個のセグメントで表示できる数字は2、3、5、7、8であり、その中で最